"""XML request builder for the Garanti BBVA GVP API.

Converts :class:`garantipay.models.Request` dataclass instances into the
XML string format expected by the GVP ``VPServlet`` endpoint.

The generated XML follows the ``GVPSRequest`` schema defined by Garanti BBVA.
Only non-``None`` fields are included in the output.

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 unless building custom integrations.
"""

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

from garantipay.models import Request


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


def build_request_xml(request: Request) -> str:
    """Serialize a :class:`~garantipay.models.Request` into a GVPSRequest XML string.

    Args:
        request: A fully populated request object. At minimum, the
            ``mode``, ``terminal``, ``customer``, and ``transaction``
            fields should be set.

    Returns:
        A Unicode XML string ready to be sent as the HTTP POST body
        to the Garanti BBVA API endpoint.

    Example::

        from garantipay.models import Request, Terminal, Transaction, Customer
        from garantipay.xml_builder import build_request_xml

        request = Request(
            mode="TEST",
            version="v1.0",
            terminal=Terminal(id="12345678", merchant_id="1234567"),
            customer=Customer(ip_address="1.2.3.4"),
            transaction=Transaction(type="sales", amount="100"),
        )
        xml_string = build_request_xml(request)
    """
    root = ET.Element("GVPSRequest")
    _build_element(root, "Mode", request.mode)
    _build_element(root, "Version", request.version)
    _build_element(root, "ChannelCode", request.channel_code)
    _build_terminal(root, request)
    _build_customer(root, request)
    _build_card(root, request)
    _build_order(root, request)
    _build_transaction(root, request)
    return ET.tostring(root, encoding="unicode")


def _build_terminal(root: ET.Element, request: Request) -> None:
    """Append the ``<Terminal>`` section to the XML tree."""
    if not request.terminal:
        return
    terminal_elem = ET.SubElement(root, "Terminal")
    _build_element(terminal_elem, "MerchantID", request.terminal.merchant_id)
    _build_element(terminal_elem, "ProvUserID", request.terminal.prov_user_id)
    _build_element(terminal_elem, "UserID", request.terminal.user_id)
    _build_element(terminal_elem, "ID", request.terminal.id)
    _build_element(terminal_elem, "HashData", request.terminal.hash_data)


def _build_customer(root: ET.Element, request: Request) -> None:
    """Append the ``<Customer>`` section to the XML tree."""
    if not request.customer:
        return
    customer_elem = ET.SubElement(root, "Customer")
    _build_element(customer_elem, "IPAddress", request.customer.ip_address)
    _build_element(customer_elem, "EmailAddress", request.customer.email_address)


def _build_card(root: ET.Element, request: Request) -> None:
    """Append the ``<Card>`` section to the XML tree."""
    if not request.card:
        return
    card_elem = ET.SubElement(root, "Card")
    _build_element(card_elem, "Number", request.card.number)
    _build_element(card_elem, "ExpireDate", request.card.expire_date)
    _build_element(card_elem, "CVV2", request.card.cvv2)


def _build_order(root: ET.Element, request: Request) -> None:
    """Append the ``<Order>`` section to the XML tree."""
    if not request.order:
        return
    order_elem = ET.SubElement(root, "Order")
    _build_element(order_elem, "OrderID", request.order.order_id)
    _build_element(order_elem, "GroupID", request.order.group_id)
    if not request.order.address_list:
        return
    address_list_elem = ET.SubElement(order_elem, "AddressList")
    if not request.order.address_list.address:
        return
    address_elem = ET.SubElement(address_list_elem, "Address")
    addr = request.order.address_list.address
    _build_element(address_elem, "Type", addr.type)
    _build_element(address_elem, "Name", addr.name)
    _build_element(address_elem, "LastName", addr.last_name)
    _build_element(address_elem, "Company", addr.company)
    _build_element(address_elem, "Text", addr.text)
    _build_element(address_elem, "City", addr.city)
    _build_element(address_elem, "District", addr.district)
    _build_element(address_elem, "Country", addr.country)
    _build_element(address_elem, "PostalCode", addr.postal_code)
    _build_element(address_elem, "PhoneNumber", addr.phone_number)
    _build_element(address_elem, "GsmNumber", addr.gsm_number)
    _build_element(address_elem, "FaxNumber", addr.fax_number)


def _build_transaction(root: ET.Element, request: Request) -> None:
    """Append the ``<Transaction>`` section to the XML tree."""
    if not request.transaction:
        return
    txn_elem = ET.SubElement(root, "Transaction")
    _build_element(txn_elem, "Type", request.transaction.type)
    _build_element(txn_elem, "SubType", request.transaction.sub_type)
    _build_element(txn_elem, "FirmCardNo", request.transaction.firm_card_no)
    _build_element(txn_elem, "InstallmentCnt", request.transaction.installment_cnt)
    _build_element(txn_elem, "Amount", request.transaction.amount)
    _build_element(txn_elem, "CurrencyCode", request.transaction.currency_code)
    _build_element(txn_elem, "CardholderPresentCode", request.transaction.cardholder_present_code)
    _build_element(txn_elem, "MotoInd", request.transaction.moto_ind)
    _build_element(txn_elem, "Description", request.transaction.description)
    if not request.transaction.secure_3d:
        return
    secure_3d_elem = ET.SubElement(txn_elem, "Secure3D")
    _build_element(secure_3d_elem, "AuthenticationCode", request.transaction.secure_3d.authentication_code)
    _build_element(secure_3d_elem, "SecurityLevel", request.transaction.secure_3d.security_level)
    _build_element(secure_3d_elem, "TxnID", request.transaction.secure_3d.txn_id)
    _build_element(secure_3d_elem, "Md", request.transaction.secure_3d.md)
