import base64
import json
import secrets
import time
from dataclasses import dataclass
from typing import Any

import aiohttp

from core.config import config
from database.crud import get_setting
from database.models import Order

try:
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding
except Exception:  # pragma: no cover - optional runtime dependency
    hashes = None
    serialization = None
    padding = None


MIDASBUY_ORDER_ENDPOINTS = {
    "production": "https://api-payments.midasbuy.com/midasbuy/v2/orders",
    "sandbox": "https://sandbox-api-payments.midasbuy.com/midasbuy/v2/orders",
}

STATUS_ALIASES = {
    "created": "created",
    "paid": "paid",
    "finished": "finished",
    "completed": "finished",
    "refunded": "refunded",
    "payment_failed": "payment_failed",
    "paymentfailed": "payment_failed",
    "failed": "payment_failed",
    "cancelled": "payment_failed",
    "canceled": "payment_failed",
    "paid_pending_delivery": "paid_pending_delivery",
}

STATUS_FA = {
    "created": "در انتظار پرداخت",
    "paid": "پرداخت موفق، آماده تحویل",
    "finished": "تحویل کامل شد",
    "refunded": "ریباند/برگشت وجه",
    "payment_failed": "پرداخت ناموفق",
    "paid_pending_delivery": "پرداخت موفق، نیازمند تحویل دستی",
}


@dataclass
class MidasbuyOpenApiConfig:
    env: str
    app_id: str
    serial_no: str
    private_key: str
    private_key_path: str

    @property
    def endpoint(self) -> str:
        return MIDASBUY_ORDER_ENDPOINTS.get(self.env, MIDASBUY_ORDER_ENDPOINTS["sandbox"])

    @property
    def ready(self) -> bool:
        return bool(self.app_id and self.serial_no and (self.private_key or self.private_key_path))


async def get_openapi_config(session) -> MidasbuyOpenApiConfig:
    env = await get_setting(session, "midasbuy_openapi_env", config.midasbuy_openapi_env)
    app_id = await get_setting(session, "midasbuy_app_id", config.midasbuy_app_id)
    serial_no = await get_setting(session, "midasbuy_serial_no", config.midasbuy_serial_no)
    private_key = await get_setting(session, "midasbuy_private_key", config.midasbuy_private_key)
    private_key_path = await get_setting(session, "midasbuy_private_key_path", config.midasbuy_private_key_path)
    env = env if env in MIDASBUY_ORDER_ENDPOINTS else "sandbox"
    return MidasbuyOpenApiConfig(env, app_id, serial_no, private_key, private_key_path)


def normalize_midasbuy_status(value: Any) -> str:
    if value is None:
        return "unknown"
    text = str(value).strip().lower().replace(" ", "_").replace("-", "_")
    return STATUS_ALIASES.get(text, text)


def midasbuy_status_label(status: str | None) -> str:
    normalized = normalize_midasbuy_status(status)
    return STATUS_FA.get(normalized, status or "ثبت نشده")


def extract_status(payload: Any) -> str:
    if not isinstance(payload, dict):
        return "unknown"
    candidates = [
        payload.get("status"),
        payload.get("order_status"),
        payload.get("orderStatus"),
        payload.get("payment_status"),
        payload.get("paymentStatus"),
    ]
    data = payload.get("data")
    if isinstance(data, dict):
        candidates.extend([
            data.get("status"),
            data.get("order_status"),
            data.get("orderStatus"),
            data.get("payment_status"),
            data.get("paymentStatus"),
        ])
    for candidate in candidates:
        if candidate:
            return normalize_midasbuy_status(candidate)
    return "unknown"


def _load_private_key(openapi_config: MidasbuyOpenApiConfig):
    if serialization is None:
        raise RuntimeError("cryptography package is not installed")

    raw_key = openapi_config.private_key
    if not raw_key and openapi_config.private_key_path:
        with open(openapi_config.private_key_path, "rb") as key_file:
            raw_key_bytes = key_file.read()
    else:
        raw_key = raw_key.replace("\\n", "\n")
        raw_key_bytes = raw_key.encode("utf-8")

    return serialization.load_pem_private_key(raw_key_bytes, password=None)


def _signature_payload(method: str, path: str, timestamp: str, nonce: str, body: str) -> str:
    return "\n".join([method.upper(), path, timestamp, nonce, body]) + "\n"


def _build_authorization(openapi_config: MidasbuyOpenApiConfig, method: str, path: str, body: str) -> tuple[str, str, str]:
    if hashes is None or padding is None:
        raise RuntimeError("cryptography package is not installed")

    timestamp = str(int(time.time()))
    nonce = secrets.token_hex(16).upper()
    private_key = _load_private_key(openapi_config)
    signature_bytes = private_key.sign(
        _signature_payload(method, path, timestamp, nonce, body).encode("utf-8"),
        padding.PKCS1v15(),
        hashes.SHA256(),
    )
    signature = base64.b64encode(signature_bytes).decode("ascii")
    auth = (
        "TXGW-SHA256-RSA2048 "
        f"auth_id={openapi_config.app_id},"
        "auth_id_type=APP_ID,"
        f"nonce_str={nonce},"
        f"timestamp={timestamp},"
        f"serial_no={openapi_config.serial_no},"
        f"signature={signature}"
    )
    return auth, timestamp, nonce


async def query_midasbuy_order(session, *, order_no: str | None = None, out_trade_no: str | None = None, payload: dict | None = None) -> dict:
    # وب‌سرویس جدید گیفت کلاب امکان استعلام مجدد سفارش را از طریق API ندارد.
    return {
        "ok": False,
        "message": "وب‌سرویس جدید گیفت‌کلاب امکان استعلام مجدد سفارش را ندارد."
    }


def create_checkout_payload(order: Order, user_openid: str | None = None) -> dict:
    return {
        "out_trade_no": f"bot_order_{order.id}",
        "order_id": order.id,
        "amount": order.amount,
        "currency": "IRR",
        "openid": user_openid,
        "metadata": {
            "telegram_user_id": order.user_id,
            "payment_method": order.payment_method,
        },
    }


def record_midasbuy_event(order: Order, status: str, payload: dict | None = None) -> None:
    normalized = normalize_midasbuy_status(status)
    order.midasbuy_status = normalized
    if payload:
        order.midasbuy_payload = json.dumps(payload, ensure_ascii=False)
        order_no = payload.get("order_no") or payload.get("orderNo")
        order_hash = payload.get("order_no_hash") or payload.get("orderNoHash")
        data = payload.get("data")
        if isinstance(data, dict):
            order_no = order_no or data.get("order_no") or data.get("orderNo")
            order_hash = order_hash or data.get("order_no_hash") or data.get("orderNoHash")
        if order_no:
            order.midasbuy_order_no = str(order_no)
        if order_hash:
            order.midasbuy_order_hash = str(order_hash)
