"""XML response parser for the Garanti BBVA GVP API.

Converts raw XML response strings from the GVP ``VPServlet`` endpoint
into structured :class:`garantipay.models.Response` dataclass instances.

This module also provides a utility to re-serialize a parsed response
back into pretty-printed XML for logging or debugging.

This module is used internally by :class:`garantipay.client.API` and
:class:`garantipay.client.GarantiClient`. You typically do not need to
call these functions directly.
"""

import xml.etree.ElementTree as ET
from typing import Optional

from garantipay.exceptions import GarantiPayXMLError
from garantipay.models import (
    Card,
    Customer,
    Order,
    Response,
    ResponseTransaction,
    RewardInqResult,
    Terminal,
    TransactionResponse,
)


def _get_element_text(parent: ET.Element, tag: str) -> Optional[str]:
    """Extract text content from a child element, or ``None`` if missing."""
    elem = parent.find(tag)
    return elem.text if elem is not None else None


def parse_response_xml(xml_string: str) -> Response:
    """Parse a GVPSResponse XML string into a :class:`~garantipay.models.Response`.

    Args:
        xml_string: Raw XML response body from the Garanti BBVA API.

    Returns:
        A populated :class:`~garantipay.models.Response` object.

    Raises:
        GarantiPayXMLError: If the XML cannot be parsed.

    Example::

        from garantipay.xml_parser import parse_response_xml

        response = parse_response_xml(raw_xml)
        if response.is_successful:
            print(f"Approved! Ref: {response.transaction.retref_num}")
    """
    try:
        root = ET.fromstring(xml_string)
    except ET.ParseError as exc:
        raise GarantiPayXMLError(
            f"Failed to parse API response as XML: {exc}"
        ) from exc
    response = Response()
    response.mode = _get_element_text(root, "Mode")
    _parse_terminal(root, response)
    _parse_customer(root, response)
    _parse_card(root, response)
    _parse_order(root, response)
    _parse_transaction(root, response)
    return response


def _parse_terminal(root: ET.Element, response: Response) -> None:
    """Parse the ``<Terminal>`` section from the XML tree."""
    terminal_elem = root.find("Terminal")
    if terminal_elem is None:
        return
    response.terminal = Terminal(
        merchant_id=_get_element_text(terminal_elem, "MerchantID"),
        prov_user_id=_get_element_text(terminal_elem, "ProvUserID"),
        user_id=_get_element_text(terminal_elem, "UserID"),
        id=_get_element_text(terminal_elem, "ID"),
        hash_data=_get_element_text(terminal_elem, "HashData"),
    )


def _parse_customer(root: ET.Element, response: Response) -> None:
    """Parse the ``<Customer>`` section from the XML tree."""
    customer_elem = root.find("Customer")
    if customer_elem is None:
        return
    response.customer = Customer(
        ip_address=_get_element_text(customer_elem, "IPAddress"),
        email_address=_get_element_text(customer_elem, "EmailAddress"),
    )


def _parse_card(root: ET.Element, response: Response) -> None:
    """Parse the ``<Card>`` section from the XML tree."""
    card_elem = root.find("Card")
    if card_elem is None:
        return
    response.card = Card(
        number=_get_element_text(card_elem, "Number"),
        expire_date=_get_element_text(card_elem, "ExpireDate"),
        cvv2=_get_element_text(card_elem, "CVV2"),
    )


def _parse_order(root: ET.Element, response: Response) -> None:
    """Parse the ``<Order>`` section from the XML tree."""
    order_elem = root.find("Order")
    if order_elem is None:
        return
    response.order = Order(
        order_id=_get_element_text(order_elem, "OrderID"),
        group_id=_get_element_text(order_elem, "GroupID"),
    )


def _parse_transaction(root: ET.Element, response: Response) -> None:
    """Parse the ``<Transaction>`` section from the XML tree."""
    txn_elem = root.find("Transaction")
    if txn_elem is None:
        return
    txn_response = TransactionResponse()
    resp_elem = txn_elem.find("Response")
    if resp_elem is not None:
        txn_response.response = ResponseTransaction(
            source=_get_element_text(resp_elem, "Source"),
            code=_get_element_text(resp_elem, "Code"),
            reason_code=_get_element_text(resp_elem, "ReasonCode"),
            message=_get_element_text(resp_elem, "Message"),
            error_msg=_get_element_text(resp_elem, "ErrorMsg"),
            sys_err_msg=_get_element_text(resp_elem, "SysErrMsg"),
        )
    txn_response.retref_num = _get_element_text(txn_elem, "RetrefNum")
    txn_response.auth_code = _get_element_text(txn_elem, "AuthCode")
    txn_response.batch_num = _get_element_text(txn_elem, "BatchNum")
    txn_response.sequence_num = _get_element_text(txn_elem, "SequenceNum")
    txn_response.prov_date = _get_element_text(txn_elem, "ProvDate")
    txn_response.card_number_masked = _get_element_text(txn_elem, "CardNumberMasked")
    txn_response.card_holder_name = _get_element_text(txn_elem, "CardHolderName")
    txn_response.card_type = _get_element_text(txn_elem, "CardType")
    txn_response.hash_data = _get_element_text(txn_elem, "HashData")
    txn_response.host_msg_list = _get_element_text(txn_elem, "HostMsgList")
    txn_response.garanti_card_ind = _get_element_text(txn_elem, "GarantiCardInd")
    reward_elem = txn_elem.find("RewardInqResult")
    if reward_elem is not None:
        txn_response.reward_inq_result = RewardInqResult(
            reward_list=_get_element_text(reward_elem, "RewardList"),
            cheque_list=_get_element_text(reward_elem, "ChequeList"),
        )
    response.transaction = txn_response


def response_to_pretty_xml(response: Response) -> str:
    """Serialize a :class:`~garantipay.models.Response` into pretty-printed XML.

    Useful for logging, debugging, or displaying the full API response
    in a human-readable format.

    Args:
        response: A parsed response object.

    Returns:
        An indented XML string representation of the response.

    Example::

        from garantipay.xml_parser import response_to_pretty_xml

        pretty = response_to_pretty_xml(response)
        print(pretty)
    """
    root = ET.Element("GVPSResponse")
    _build_element = _build_optional_element
    _build_element(root, "Mode", response.mode)
    if response.terminal:
        terminal_elem = ET.SubElement(root, "Terminal")
        _build_element(terminal_elem, "MerchantID", response.terminal.merchant_id)
        _build_element(terminal_elem, "ProvUserID", response.terminal.prov_user_id)
        _build_element(terminal_elem, "UserID", response.terminal.user_id)
        _build_element(terminal_elem, "ID", response.terminal.id)
    if response.customer:
        customer_elem = ET.SubElement(root, "Customer")
        _build_element(customer_elem, "IPAddress", response.customer.ip_address)
        _build_element(customer_elem, "EmailAddress", response.customer.email_address)
    if response.card:
        card_elem = ET.SubElement(root, "Card")
        _build_element(card_elem, "Number", response.card.number)
        _build_element(card_elem, "ExpireDate", response.card.expire_date)
        _build_element(card_elem, "CVV2", response.card.cvv2)
    if response.order:
        order_elem = ET.SubElement(root, "Order")
        _build_element(order_elem, "OrderID", response.order.order_id)
        _build_element(order_elem, "GroupID", response.order.group_id)
    if response.transaction:
        txn_elem = ET.SubElement(root, "Transaction")
        if response.transaction.response:
            resp_elem = ET.SubElement(txn_elem, "Response")
            _build_element(resp_elem, "Source", response.transaction.response.source)
            _build_element(resp_elem, "Code", response.transaction.response.code)
            _build_element(resp_elem, "ReasonCode", response.transaction.response.reason_code)
            _build_element(resp_elem, "Message", response.transaction.response.message)
            _build_element(resp_elem, "ErrorMsg", response.transaction.response.error_msg)
            _build_element(resp_elem, "SysErrMsg", response.transaction.response.sys_err_msg)
        _build_element(txn_elem, "RetrefNum", response.transaction.retref_num)
        _build_element(txn_elem, "AuthCode", response.transaction.auth_code)
        _build_element(txn_elem, "BatchNum", response.transaction.batch_num)
        _build_element(txn_elem, "SequenceNum", response.transaction.sequence_num)
        _build_element(txn_elem, "ProvDate", response.transaction.prov_date)
        _build_element(txn_elem, "CardNumberMasked", response.transaction.card_number_masked)
        _build_element(txn_elem, "CardHolderName", response.transaction.card_holder_name)
        _build_element(txn_elem, "CardType", response.transaction.card_type)
        _build_element(txn_elem, "HashData", response.transaction.hash_data)
        _build_element(txn_elem, "HostMsgList", response.transaction.host_msg_list)
        _build_element(txn_elem, "GarantiCardInd", response.transaction.garanti_card_ind)
    ET.indent(root, space="  ")
    return ET.tostring(root, encoding="unicode")


def _build_optional_element(parent: ET.Element, tag: str, value: Optional[str]) -> None:
    """Append a child element only if *value* is not ``None``."""
    if value is not None:
        elem = ET.SubElement(parent, tag)
        elem.text = value
