"""
FDE SDK - Type definitions for all request and response models.

Mirrors the OpenAPI schema exactly. Uses dataclasses for request types and
TypedDicts for response types. No Pydantic dependency required.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional


# ---------------------------------------------------------------------------
# Shared sub-models
# ---------------------------------------------------------------------------


@dataclass
class CustKV:
    """Custom key-value pair passed in eventDetail sub-models."""

    key: str
    value: str

    def to_dict(self) -> Dict[str, str]:
        return {"key": self.key, "value": self.value}


@dataclass
class AddressModel:
    """Billing or delivery address."""

    Country: Optional[str] = None
    City: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        return {
            k: v
            for k, v in {"Country": self.Country, "City": self.City}.items()
            if v is not None
        }


@dataclass
class PersonModel:
    """Person information (name, gender)."""

    Name: Optional[str] = None
    Gender: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        return {
            k: v
            for k, v in {"Name": self.Name, "Gender": self.Gender}.items()
            if v is not None
        }


@dataclass
class UserInfoModel:
    """User information (level, type)."""

    UserLevel: Optional[str] = None
    UserType: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        return {
            k: v
            for k, v in {"UserLevel": self.UserLevel, "UserType": self.UserType}.items()
            if v is not None
        }


@dataclass
class ResultStatus:
    """Result status with optional failure reason."""

    Status: str
    FailureReason: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {"Status": self.Status}
        if self.FailureReason is not None:
            d["FailureReason"] = self.FailureReason
        return d


# ---------------------------------------------------------------------------
# EventDetail sub-models
# ---------------------------------------------------------------------------


@dataclass
class RegisterDetail:
    """EventDetail.Register - new user registration event."""

    UserInfo: Optional[UserInfoModel] = None
    Person: Optional[PersonModel] = None
    BillingAddress: Optional[AddressModel] = None
    DeliveryAddress: Optional[AddressModel] = None
    Cust: Optional[List[CustKV]] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        if self.UserInfo is not None:
            d["UserInfo"] = self.UserInfo.to_dict()
        if self.Person is not None:
            d["Person"] = self.Person.to_dict()
        if self.BillingAddress is not None:
            d["BillingAddress"] = self.BillingAddress.to_dict()
        if self.DeliveryAddress is not None:
            d["DeliveryAddress"] = self.DeliveryAddress.to_dict()
        if self.Cust is not None:
            d["Cust"] = [c.to_dict() for c in self.Cust]
        return d


@dataclass
class LoginDetail:
    """EventDetail.Login - user login event."""

    UserLoginName: Optional[str] = None
    UserInfo: Optional[UserInfoModel] = None
    Cust: Optional[List[CustKV]] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        if self.UserLoginName is not None:
            d["UserLoginName"] = self.UserLoginName
        if self.UserInfo is not None:
            d["UserInfo"] = self.UserInfo.to_dict()
        if self.Cust is not None:
            d["Cust"] = [c.to_dict() for c in self.Cust]
        return d


@dataclass
class RegisterResultDetail:
    """EventDetail.RegisterResult - outcome of a registration attempt."""

    RegisterResult: Optional[ResultStatus] = None
    Status: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        if self.RegisterResult is not None:
            d["RegisterResult"] = self.RegisterResult.to_dict()
        if self.Status is not None:
            d["Status"] = self.Status
        return d


@dataclass
class LoginResultDetail:
    """EventDetail.LoginResult - outcome of a login attempt."""

    LoginResult: Optional[ResultStatus] = None
    Status: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        if self.LoginResult is not None:
            d["LoginResult"] = self.LoginResult.to_dict()
        if self.Status is not None:
            d["Status"] = self.Status
        return d


@dataclass
class PasswordChangeDetail:
    """EventDetail.PasswordChange."""

    UserInfo: Optional[UserInfoModel] = None
    ChangeReason: Optional[str] = None
    Cust: Optional[List[CustKV]] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        if self.UserInfo is not None:
            d["UserInfo"] = self.UserInfo.to_dict()
        if self.ChangeReason is not None:
            d["ChangeReason"] = self.ChangeReason
        if self.Cust is not None:
            d["Cust"] = [c.to_dict() for c in self.Cust]
        return d


@dataclass
class EmailChangeDetail:
    """EventDetail.EmailChange."""

    UserInfo: Optional[UserInfoModel] = None
    OldEmail: Optional[str] = None
    NewEmail: Optional[str] = None
    ChangeReason: Optional[str] = None
    Cust: Optional[List[CustKV]] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        if self.UserInfo is not None:
            d["UserInfo"] = self.UserInfo.to_dict()
        if self.OldEmail is not None:
            d["OldEmail"] = self.OldEmail
        if self.NewEmail is not None:
            d["NewEmail"] = self.NewEmail
        if self.ChangeReason is not None:
            d["ChangeReason"] = self.ChangeReason
        if self.Cust is not None:
            d["Cust"] = [c.to_dict() for c in self.Cust]
        return d


@dataclass
class TransactionDetail:
    """EventDetail.Transaction - payment/transfer transaction event."""

    TransactionId: Optional[str] = None
    TransactionType: Optional[str] = None
    Amount: Optional[float] = None
    Currency: Optional[str] = None
    MerchantId: Optional[str] = None
    MerchantName: Optional[str] = None
    MerchantCategory: Optional[str] = None
    BillingAddress: Optional[AddressModel] = None
    DeliveryAddress: Optional[AddressModel] = None
    PaymentMethod: Optional[str] = None
    Cust: Optional[List[CustKV]] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        simple_fields = [
            "TransactionId",
            "TransactionType",
            "Amount",
            "Currency",
            "MerchantId",
            "MerchantName",
            "MerchantCategory",
            "PaymentMethod",
        ]
        for f in simple_fields:
            v = getattr(self, f)
            if v is not None:
                d[f] = v
        if self.BillingAddress is not None:
            d["BillingAddress"] = self.BillingAddress.to_dict()
        if self.DeliveryAddress is not None:
            d["DeliveryAddress"] = self.DeliveryAddress.to_dict()
        if self.Cust is not None:
            d["Cust"] = [c.to_dict() for c in self.Cust]
        return d


@dataclass
class SecurityVerificationDetail:
    """EventDetail.SecurityVerification - CAPTCHA/OTP check."""

    VerificationEvent: Optional[str] = None
    VerificationType: Optional[str] = None
    VerificationContent: Optional[str] = None
    VerificationResult: Optional[str] = None
    Cust: Optional[List[CustKV]] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        for f in [
            "VerificationEvent",
            "VerificationType",
            "VerificationContent",
            "VerificationResult",
        ]:
            v = getattr(self, f)
            if v is not None:
                d[f] = v
        if self.Cust is not None:
            d["Cust"] = [c.to_dict() for c in self.Cust]
        return d


@dataclass
class PaymentResultDetail:
    """EventDetail.PaymentResult - outcome of a payment."""

    PaymentResult: Optional[ResultStatus] = None
    Status: Optional[str] = None
    TransactionId: Optional[str] = None
    ErrorCode: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        if self.PaymentResult is not None:
            d["PaymentResult"] = self.PaymentResult.to_dict()
        for f in ["Status", "TransactionId", "ErrorCode"]:
            v = getattr(self, f)
            if v is not None:
                d[f] = v
        return d


@dataclass
class SmsResultDetail:
    """EventDetail.SmsResult - outcome of an SMS OTP."""

    SmsResult: Optional[ResultStatus] = None
    Status: Optional[str] = None
    SmsType: Optional[str] = None
    AttemptCount: Optional[int] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        if self.SmsResult is not None:
            d["SmsResult"] = self.SmsResult.to_dict()
        for f in ["Status", "SmsType", "AttemptCount"]:
            v = getattr(self, f)
            if v is not None:
                d[f] = v
        return d


@dataclass
class EventDetailModel:
    """
    EventDetail wrapper - exactly one sub-field should be populated
    per event type (Register, Login, Transaction, etc.).
    """

    Register: Optional[RegisterDetail] = None
    Login: Optional[LoginDetail] = None
    RegisterResult: Optional[RegisterResultDetail] = None
    LoginResult: Optional[LoginResultDetail] = None
    PasswordChange: Optional[PasswordChangeDetail] = None
    EmailChange: Optional[EmailChangeDetail] = None
    Transaction: Optional[TransactionDetail] = None
    SecurityVerification: Optional[SecurityVerificationDetail] = None
    PaymentResult: Optional[PaymentResultDetail] = None
    SmsResult: Optional[SmsResultDetail] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        mapping = {
            "Register": self.Register,
            "Login": self.Login,
            "RegisterResult": self.RegisterResult,
            "LoginResult": self.LoginResult,
            "PasswordChange": self.PasswordChange,
            "EmailChange": self.EmailChange,
            "Transaction": self.Transaction,
            "SecurityVerification": self.SecurityVerification,
            "PaymentResult": self.PaymentResult,
            "SmsResult": self.SmsResult,
        }
        for key, val in mapping.items():
            if val is not None:
                d[key] = val.to_dict()
        return d


# ---------------------------------------------------------------------------
# Request models
# ---------------------------------------------------------------------------


@dataclass
class EvaluateRequest:
    """Request body for POST /v1/risk/evaluate."""

    eventCode: str
    sessionId: str
    deviceToken: str
    eventDetail: EventDetailModel
    eventTime: Optional[str] = None
    deviceProvider: Optional[str] = None
    userIp: Optional[str] = None
    userId: Optional[str] = None
    userEmail: Optional[str] = None
    userPhone: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {
            "eventCode": self.eventCode,
            "sessionId": self.sessionId,
            "deviceToken": self.deviceToken,
            "eventDetail": self.eventDetail.to_dict(),
        }
        for f in [
            "eventTime",
            "deviceProvider",
            "userIp",
            "userId",
            "userEmail",
            "userPhone",
        ]:
            v = getattr(self, f)
            if v is not None:
                d[f] = v
        return d


@dataclass
class OutcomeRequest:
    """Request body for POST /v1/risk/outcome."""

    eventCode: str
    sessionId: str
    deviceToken: str
    eventDetail: EventDetailModel
    eventTime: Optional[str] = None
    userIp: Optional[str] = None
    userId: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {
            "eventCode": self.eventCode,
            "sessionId": self.sessionId,
            "deviceToken": self.deviceToken,
            "eventDetail": self.eventDetail.to_dict(),
        }
        for f in ["eventTime", "userIp", "userId"]:
            v = getattr(self, f)
            if v is not None:
                d[f] = v
        return d


@dataclass
class EnhancedDecisionItem:
    """Single label item for /v1/risk/labels."""

    requestId: str
    result: str
    description: Optional[str] = None
    labelSource: Optional[str] = "ground_truth"
    labelConfidence: Optional[float] = 1.0
    sourceDetail: Optional[str] = None
    supersedes: Optional[str] = None
    labeledBy: Optional[str] = None
    labelNotes: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {"requestId": self.requestId, "result": self.result}
        optional_fields = [
            "description",
            "labelSource",
            "labelConfidence",
            "sourceDetail",
            "supersedes",
            "labeledBy",
            "labelNotes",
        ]
        for f in optional_fields:
            v = getattr(self, f)
            if v is not None:
                d[f] = v
        return d


@dataclass
class SubmitLabelsRequest:
    """Request body for POST /v1/risk/labels (max 100 items)."""

    decisionList: List[EnhancedDecisionItem] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        return {"decisionList": [item.to_dict() for item in self.decisionList]}


# ---------------------------------------------------------------------------
# Response types (TypedDicts — read-only results from the server)
# ---------------------------------------------------------------------------


class BankingRuleDetail(Dict[str, Any]):
    """Typing alias for a banking rule detail dict from the response."""


class ExplanationTopFactor(Dict[str, Any]):
    """Typing alias for a SHAP top-factor dict."""


class ExplanationResponse(Dict[str, Any]):
    """Typing alias for a SHAP explanation response dict."""


class RiskEvaluateResponse(Dict[str, Any]):
    """
    Response from POST /v1/risk/evaluate.

    Guaranteed keys: request_id (str), risk_score (int),
    risk_level (str), decision (str).
    Optional keys: risk_labels, banking_rule_details, data_quality,
    explanation, list_actions.
    """


class OutcomeResponse(Dict[str, Any]):
    """
    Response from POST /v1/risk/outcome.

    Guaranteed key: request_id (str).
    """


class LabelResult(Dict[str, Any]):
    """Single label result dict."""


class SubmitLabelsResponse(Dict[str, Any]):
    """
    Response from POST /v1/risk/labels.

    Guaranteed key: request_id (str).
    Optional keys: results (list), created (int), not_found (int), errors (int).
    """
