"""Data models for the Garanti BBVA Switch (Card Storage) JSON API.

These models represent the request and response structures for the
Switch API, which handles card tokenization and token-based payments.

Unlike the VPServlet XML API models in :mod:`garantipay.models`, these
models are serialized to/from JSON and use SHA-256 based authentication.

Example::

    from garantipay.switch_models import SwitchCard, SwitchCardHolder

    card = SwitchCard(number="5549601634451019", expire_month="02", expire_year="25")
    holder = SwitchCardHolder(ip="192.168.1.1", email="customer@example.com")
"""

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


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


@dataclass
class SwitchHeader:
    """Header included in every Switch API request.

    The ``hashedData`` field is automatically computed by
    :class:`~garantipay.switch_client.SwitchClient`. You typically
    do not need to populate it manually.

    Attributes:
        request_id: Unique identifier for this request (max 36 chars).
            Auto-generated by ``SwitchClient`` if not provided.
        swt_id: Switch ID assigned to your merchant account.
        user_id: User identifier for audit/tracking purposes.
        timestamp: Request timestamp string.
        hashed_data: SHA-256 hash for request authentication.
    """

    request_id: str = ""
    swt_id: str = ""
    user_id: str = ""
    timestamp: str = ""
    hashed_data: str = ""

    def to_dict(self) -> Dict[str, str]:
        """Serialize to the JSON dict format expected by the API."""
        return {
            "requestId": self.request_id,
            "swtId": self.swt_id,
            "userId": self.user_id,
            "timestamp": self.timestamp,
            "hashedData": self.hashed_data,
        }


@dataclass
class SwitchCard:
    """Card information for token generation or direct payment.

    For token generation with full card data, set ``number``,
    ``expire_month``, and ``expire_year``.

    For token-based payments, set only ``token``.

    Attributes:
        number: Full card number (PAN). Required for ``registrationType="F"``.
        expire_month: Card expiration month (``"MM"``). Required for full card.
        expire_year: Card expiration year (``"YY"``). Required for full card.
        cvv: Card security code (3 digits). Optional.
        holder_name: Name printed on the card. Optional.
        first6: First 6 digits of the card. Used with ``registrationType="M"``.
        last4: Last 4 digits of the card. Used with ``registrationType="M"``.
        token: Token ID from a previous token generation. Used for
            token-based payments instead of raw card data.
    """

    number: Optional[str] = None
    expire_month: Optional[str] = None
    expire_year: Optional[str] = None
    cvv: Optional[str] = None
    holder_name: Optional[str] = None
    first6: Optional[str] = None
    last4: Optional[str] = None
    token: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to the JSON dict format expected by the API."""
        result: Dict[str, Any] = {}
        if self.number is not None:
            result["number"] = self.number
        if self.expire_month is not None:
            result["expireMonth"] = self.expire_month
        if self.expire_year is not None:
            result["expireYear"] = self.expire_year
        if self.cvv is not None:
            result["cvv"] = self.cvv
        if self.holder_name is not None:
            result["holderName"] = self.holder_name
        if self.first6 is not None:
            result["first6"] = self.first6
        if self.last4 is not None:
            result["last4"] = self.last4
        if self.token is not None:
            result["token"] = self.token
        return result


@dataclass
class SwitchCardHolder:
    """Cardholder (customer) information for Switch API requests.

    Attributes:
        ip: Customer's IP address in IPv4 format. Required for payments.
        email: Customer's email address.
        name: Customer's first name.
        last_name: Customer's last name.
        gsm: Customer's mobile phone number.
        customer_number: Garanti Bank customer number.
        national_number: Turkish national ID number (TCKN).
    """

    ip: Optional[str] = None
    email: Optional[str] = None
    name: Optional[str] = None
    last_name: Optional[str] = None
    gsm: Optional[str] = None
    customer_number: Optional[str] = None
    national_number: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to the JSON dict format expected by the API."""
        result: Dict[str, Any] = {}
        if self.ip is not None:
            result["ip"] = self.ip
        if self.email is not None:
            result["email"] = self.email
        if self.name is not None:
            result["name"] = self.name
        if self.last_name is not None:
            result["lastName"] = self.last_name
        if self.gsm is not None:
            result["gsm"] = self.gsm
        if self.customer_number is not None:
            result["customerNumber"] = self.customer_number
        if self.national_number is not None:
            result["nationalNumber"] = self.national_number
        return result


@dataclass
class SwitchAcquirer:
    """Acquirer (bank) routing information.

    Attributes:
        bank_id: Target bank ID for routing the transaction. Common values:

            - ``"62"`` -- T. Garanti Bankasi A.S.
            - ``"64"`` -- Is Bank A.S.
            - ``"46"`` -- Akbank T.A.S.
            - ``"111"`` -- QNB Finansbank A.S.
            - ``"10"`` -- T.C. Ziraat Bankasi A.S.
            - ``"12"`` -- T. Halk Bankasi A.S.

            If not specified, the Switch system routes automatically.
    """

    bank_id: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to the JSON dict format expected by the API."""
        result: Dict[str, Any] = {}
        if self.bank_id is not None:
            result["bankId"] = self.bank_id
        return result


# ---------------------------------------------------------------------------
# Response models
# ---------------------------------------------------------------------------


@dataclass
class SwitchResponseHeader:
    """Header returned in every Switch API response.

    Attributes:
        request_id: Echoed back from the request.
        swt_id: Echoed back from the request.
        return_code: Response code. ``"00"`` means success.
        reason_code: Sub-code providing more detail.
        message: Human-readable response message.
        timestamp: Unix timestamp (milliseconds) of the response.
        hashed_data: SHA-256 hash for response verification.
    """

    request_id: str = ""
    swt_id: str = ""
    return_code: str = ""
    reason_code: str = ""
    message: str = ""
    timestamp: str = ""
    hashed_data: str = ""

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SwitchResponseHeader":
        """Deserialize from an API response JSON dict."""
        return cls(
            request_id=str(data.get("requestId", "")),
            swt_id=str(data.get("swtId", "")),
            return_code=str(data.get("returnCode", "")),
            reason_code=str(data.get("reasonCode", "")),
            message=str(data.get("message", "")),
            timestamp=str(data.get("timestamp", "")),
            hashed_data=str(data.get("hashedData", "")),
        )


@dataclass
class SwitchCardResponse:
    """Card information returned in token and payment responses.

    Attributes:
        token: The generated or used token ID.
        bin_number: First 6 digits (BIN) of the card.
        bank_id: ID of the card-issuing bank.
        masked_number: Masked card number (e.g. ``"554960******7017"``).
        bank_name: Name of the card-issuing bank.
    """

    token: str = ""
    bin_number: str = ""
    bank_id: str = ""
    masked_number: str = ""
    bank_name: str = ""

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SwitchCardResponse":
        """Deserialize from an API response JSON dict."""
        return cls(
            token=str(data.get("token", "")),
            bin_number=str(data.get("binNumber", "")),
            bank_id=str(data.get("bankId", "")),
            masked_number=str(data.get("maskedNumber", "")),
            bank_name=str(data.get("bankName", "")),
        )


@dataclass
class SwitchAcquirerResponse:
    """Bank (acquirer) response details from a payment transaction.

    Attributes:
        terminal_id: Terminal ID used for the transaction.
        merchant_id: Merchant ID used for the transaction.
        order_id: Order ID assigned to the transaction.
        return_code: Bank-level response code.
        reason_code: Bank-level reason code.
        error_message: Error message from the bank, if any.
        ret_ref_num: Retrieval reference number. Keep this for
            refunds and voids.
        auth_code: Authorization code from the issuing bank.
        provision_date: Date/time the transaction was processed.
    """

    terminal_id: str = ""
    merchant_id: str = ""
    order_id: str = ""
    return_code: str = ""
    reason_code: str = ""
    error_message: str = ""
    ret_ref_num: str = ""
    auth_code: str = ""
    provision_date: str = ""

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SwitchAcquirerResponse":
        """Deserialize from an API response JSON dict."""
        return cls(
            terminal_id=str(data.get("terminalId", "")),
            merchant_id=str(data.get("merchantId", "")),
            order_id=str(data.get("orderId", "")),
            return_code=str(data.get("returnCode", "")),
            reason_code=str(data.get("reasonCode", "")),
            error_message=str(data.get("errorMessage", "")),
            ret_ref_num=str(data.get("retRefNum", "")),
            auth_code=str(data.get("authCode", "")),
            provision_date=str(data.get("provisionDate", "")),
        )


@dataclass
class SwitchResponse:
    """Base response from any Switch API call.

    All Switch API responses include a header with return/reason codes.
    Use :attr:`is_successful` to quickly check the result.

    Attributes:
        header: Response header with status codes and hash.
        error_map: Field-level validation errors, if any. Keys are
            field names (e.g. ``"card.expireMonth"``), values are
            error descriptions.
        raw_data: The complete raw JSON response dict for advanced
            inspection.
    """

    header: Optional[SwitchResponseHeader] = None
    error_map: Optional[Dict[str, str]] = None
    raw_data: Optional[Dict[str, Any]] = None

    @property
    def is_successful(self) -> bool:
        """Check whether the API call was successful.

        Returns:
            ``True`` if the return code is ``"00"``, ``False`` otherwise.
        """
        if self.header:
            return self.header.return_code == "00"
        return False

    @property
    def error_message(self) -> Optional[str]:
        """Retrieve the error message from the response header.

        Returns:
            The response message string, or ``None`` if unavailable.
        """
        if self.header:
            return self.header.message
        return None

    @property
    def return_code(self) -> Optional[str]:
        """Retrieve the return code.

        Returns:
            The return code (e.g. ``"00"`` for success), or ``None``.
        """
        if self.header:
            return self.header.return_code
        return None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SwitchResponse":
        """Deserialize from a raw API response JSON dict."""
        header = None
        if "header" in data:
            header = SwitchResponseHeader.from_dict(data["header"])
        error_map = data.get("errorMap")
        return cls(header=header, error_map=error_map, raw_data=data)


@dataclass
class SwitchTokenResponse(SwitchResponse):
    """Response from a token generation or token inquiry request.

    Extends :class:`SwitchResponse` with card/token details.

    Attributes:
        card: Card and token information returned by the API.

    Example::

        response = switch_client.generate_token(...)
        if response.is_successful:
            token = response.card.token  # Store this for future payments
            print(f"Token: {token}")
            print(f"Masked card: {response.card.masked_number}")
    """

    card: Optional[SwitchCardResponse] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SwitchTokenResponse":
        """Deserialize from a raw API response JSON dict."""
        header = None
        if "header" in data:
            header = SwitchResponseHeader.from_dict(data["header"])
        card = None
        if "card" in data:
            card = SwitchCardResponse.from_dict(data["card"])
        error_map = data.get("errorMap")
        return cls(header=header, card=card, error_map=error_map, raw_data=data)


@dataclass
class SwitchPaymentResponse(SwitchResponse):
    """Response from a token-based payment (sale) request.

    Extends :class:`SwitchResponse` with payment-specific details
    such as order ID, acquirer response, and transaction amount.

    Attributes:
        order_id: Order ID for the transaction.
        vpos_order_id: Virtual POS order ID generated by the Switch
            system. Use this when looking up the transaction in
            bank management screens.
        txn_amount: Transaction amount that was processed.
        installment_count: Number of installments applied.
        card: Card/token information echoed back.
        acquirer_response: Detailed response from the acquiring bank.
        acquirer_id: ID of the bank that processed the transaction.

    Example::

        response = switch_client.sale_with_token(...)
        if response.is_successful:
            print(f"Ref: {response.acquirer_response.ret_ref_num}")
            print(f"Auth: {response.acquirer_response.auth_code}")
    """

    order_id: str = ""
    vpos_order_id: str = ""
    txn_amount: str = ""
    installment_count: str = ""
    card: Optional[SwitchCardResponse] = None
    acquirer_response: Optional[SwitchAcquirerResponse] = None
    acquirer_id: str = ""

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "SwitchPaymentResponse":
        """Deserialize from a raw API response JSON dict."""
        header = None
        if "header" in data:
            header = SwitchResponseHeader.from_dict(data["header"])
        card = None
        if "card" in data:
            card = SwitchCardResponse.from_dict(data["card"])
        acquirer_response = None
        if "acquirerResponse" in data:
            acquirer_response = SwitchAcquirerResponse.from_dict(data["acquirerResponse"])
        error_map = data.get("errorMap")
        return cls(
            header=header,
            order_id=str(data.get("orderId", "")),
            vpos_order_id=str(data.get("vposOrderId", "")),
            txn_amount=str(data.get("txnAmount", "")),
            installment_count=str(data.get("installmentCount", "")),
            card=card,
            acquirer_response=acquirer_response,
            acquirer_id=str(data.get("acquirerId", "")),
            error_map=error_map,
            raw_data=data,
        )
