# @File: backend/routers/whmcs_extra.py
# @Desc: Extra WHMCS endpoints that wrap additional official WHMCS API actions
#        the frontend dashboard depends on. All authenticated routes scope by
#        the logged-in client — clientid is NEVER accepted from the request body.
#        Admin-only mutations (CancelOrder, AcceptOrder, ModuleSuspend, …) are
#        guarded by the admin check.
import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field

from dependencies.auth import get_current_user
from schemas.auth import UserResponse
from services.whmcs import call_whmcs, call_whmcs_safe, get_client_by_email

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/whmcs", tags=["whmcs-extra"])


def _require_admin(user: UserResponse) -> None:
    is_admin = bool(
        getattr(user, "is_admin", False)
        or getattr(user, "is_superuser", False)
        or getattr(user, "role", "") in ("admin", "superadmin")
    )
    if not is_admin:
        raise HTTPException(status_code=403, detail="Admin access required.")


async def _require_client_id(email: str) -> int:
    client = await get_client_by_email(email)
    if not client:
        raise HTTPException(status_code=404, detail="No WHMCS client found for this user.")
    client_id = client.get("userid") or client.get("id") or client.get("client_id")
    if not client_id:
        raise HTTPException(status_code=500, detail="WHMCS client id could not be resolved.")
    return int(client_id)


# ---------- SSO ----------
class SsoResponse(BaseModel):
    redirect_url: Optional[str] = None
    access_token: Optional[str] = None


@router.post("/sso", response_model=SsoResponse)
async def whmcs_sso(
    destination: Optional[str] = Query(
        None, description="Optional WHMCS destination, e.g. 'sso:custom_redirect'."
    ),
    current_user: UserResponse = Depends(get_current_user),
):
    """Create a single-sign-on token for the current user so they can
    seamlessly deep-link into their WHMCS client area."""
    client_id = await _require_client_id(current_user.email)
    payload: Dict[str, Any] = {"client_id": client_id}
    if destination:
        payload["destination"] = destination
    body = await call_whmcs("CreateSsoToken", payload)
    return SsoResponse(
        redirect_url=body.get("redirect_url"),
        access_token=body.get("access_token"),
    )


# ---------- TLD pricing ----------
@router.get("/tld-pricing")
async def whmcs_tld_pricing(currency_id: Optional[int] = Query(None, alias="currency")):
    """Public: returns the WHMCS TLD pricing matrix (register/transfer/renew)."""
    params: Dict[str, Any] = {}
    if currency_id is not None:
        params["currencyid"] = currency_id
    body = await call_whmcs_safe("GetTLDPricing", params)
    if not body:
        return {"pricing": {}, "currency": None}
    return {
        "pricing": body.get("pricing", {}),
        "currency": body.get("currency"),
    }


# ---------- Nameservers ----------
class NameserversPayload(BaseModel):
    domain_id: int = Field(..., description="WHMCS domain id (from GetClientsDomains).")
    ns1: str
    ns2: str
    ns3: Optional[str] = None
    ns4: Optional[str] = None
    ns5: Optional[str] = None


async def _assert_domain_ownership(domain_id: int, email: str) -> None:
    """Verify the domain belongs to the logged-in user's WHMCS client."""
    client_id = await _require_client_id(email)
    body = await call_whmcs_safe("GetClientsDomains", {"clientid": client_id, "limitnum": 200})
    if not body:
        raise HTTPException(status_code=404, detail="Domain not found for this client.")
    raw_list = body.get("domains")
    if isinstance(raw_list, dict):
        raw_list = raw_list.get("domain", [])
    if not isinstance(raw_list, list):
        raw_list = []
    ids = {int(d.get("id")) for d in raw_list if d.get("id") is not None}
    if int(domain_id) not in ids:
        raise HTTPException(status_code=403, detail="Domain does not belong to this user.")


@router.get("/domains/{domain_id}/nameservers")
async def whmcs_get_nameservers(
    domain_id: int,
    current_user: UserResponse = Depends(get_current_user),
):
    await _assert_domain_ownership(domain_id, current_user.email)
    body = await call_whmcs("DomainGetNameservers", {"domainid": domain_id})
    return {
        "ns1": body.get("ns1"),
        "ns2": body.get("ns2"),
        "ns3": body.get("ns3"),
        "ns4": body.get("ns4"),
        "ns5": body.get("ns5"),
    }


@router.put("/domains/{domain_id}/nameservers")
async def whmcs_update_nameservers(
    domain_id: int,
    payload: NameserversPayload,
    current_user: UserResponse = Depends(get_current_user),
):
    if payload.domain_id != domain_id:
        raise HTTPException(status_code=400, detail="Path domain_id does not match body.")
    await _assert_domain_ownership(domain_id, current_user.email)
    body = await call_whmcs(
        "DomainUpdateNameservers",
        {
            "domainid": domain_id,
            "ns1": payload.ns1,
            "ns2": payload.ns2,
            "ns3": payload.ns3,
            "ns4": payload.ns4,
            "ns5": payload.ns5,
        },
    )
    return {"result": body.get("result"), "message": body.get("message")}


# ---------- Domain register / renew / transfer (user-scoped) ----------
class DomainRenewPayload(BaseModel):
    domain_id: int
    regperiod: int = Field(1, ge=1, le=10)


@router.post("/domains/{domain_id}/renew")
async def whmcs_domain_renew(
    domain_id: int,
    payload: DomainRenewPayload,
    current_user: UserResponse = Depends(get_current_user),
):
    if payload.domain_id != domain_id:
        raise HTTPException(status_code=400, detail="Path domain_id does not match body.")
    await _assert_domain_ownership(domain_id, current_user.email)
    body = await call_whmcs(
        "DomainRenew", {"domainid": domain_id, "regperiod": payload.regperiod}
    )
    return {"result": body.get("result"), "message": body.get("message")}


# ---------- Invoices detail + payment ----------
@router.get("/invoices/{invoice_id}")
async def whmcs_invoice_detail(
    invoice_id: int,
    current_user: UserResponse = Depends(get_current_user),
):
    body = await call_whmcs("GetInvoice", {"invoiceid": invoice_id})
    # Scope check: the invoice must belong to this client.
    client_id = await _require_client_id(current_user.email)
    invoice_uid = body.get("userid") or body.get("clientid")
    if invoice_uid is not None and int(invoice_uid) != client_id:
        raise HTTPException(status_code=403, detail="Invoice does not belong to this user.")
    return body


class InvoicePaymentPayload(BaseModel):
    invoice_id: int
    transaction_id: str
    amount: float
    fees: Optional[float] = 0.0
    gateway: str = "manual"
    date: Optional[str] = None  # YYYY-MM-DD HH:MM:SS
    noemail: bool = False


@router.post("/invoices/{invoice_id}/payment")
async def whmcs_add_invoice_payment(
    invoice_id: int,
    payload: InvoicePaymentPayload,
    current_user: UserResponse = Depends(get_current_user),
):
    """Record a payment against an invoice. Admin-only to avoid client-side forging."""
    _require_admin(current_user)
    if payload.invoice_id != invoice_id:
        raise HTTPException(status_code=400, detail="Path invoice_id does not match body.")
    body = await call_whmcs(
        "AddInvoicePayment",
        {
            "invoiceid": invoice_id,
            "transid": payload.transaction_id,
            "amount": payload.amount,
            "fees": payload.fees,
            "gateway": payload.gateway,
            "date": payload.date,
            "noemail": str(payload.noemail).lower(),
        },
    )
    return {"result": body.get("result"), "message": body.get("message")}


# ---------- Orders ----------
@router.get("/orders")
async def whmcs_orders(
    limit: int = Query(25, ge=1, le=200),
    current_user: UserResponse = Depends(get_current_user),
):
    client_id = await _require_client_id(current_user.email)
    body = await call_whmcs_safe(
        "GetOrders", {"userid": client_id, "limitnum": limit}
    )
    if not body:
        return {"items": [], "total": 0}
    orders = body.get("orders")
    if isinstance(orders, dict):
        orders = orders.get("order", [])
    if not isinstance(orders, list):
        orders = []
    return {"items": orders, "total": int(body.get("totalresults") or len(orders))}


class AcceptOrderPayload(BaseModel):
    order_id: int
    send_email: bool = True


@router.post("/orders/{order_id}/accept")
async def whmcs_accept_order(
    order_id: int,
    payload: AcceptOrderPayload,
    current_user: UserResponse = Depends(get_current_user),
):
    _require_admin(current_user)
    if payload.order_id != order_id:
        raise HTTPException(status_code=400, detail="Path order_id does not match body.")
    body = await call_whmcs(
        "AcceptOrder",
        {"orderid": order_id, "sendemail": str(payload.send_email).lower()},
    )
    return {"result": body.get("result"), "message": body.get("message")}


class CancelOrderPayload(BaseModel):
    order_id: int
    cancel_sub: bool = True
    no_email: bool = False


@router.post("/orders/{order_id}/cancel")
async def whmcs_cancel_order(
    order_id: int,
    payload: CancelOrderPayload,
    current_user: UserResponse = Depends(get_current_user),
):
    _require_admin(current_user)
    if payload.order_id != order_id:
        raise HTTPException(status_code=400, detail="Path order_id does not match body.")
    body = await call_whmcs(
        "CancelOrder",
        {
            "orderid": order_id,
            "cancelsub": str(payload.cancel_sub).lower(),
            "noemail": str(payload.no_email).lower(),
        },
    )
    return {"result": body.get("result"), "message": body.get("message")}


# ---------- Module actions (admin) ----------
class ModuleActionPayload(BaseModel):
    service_id: int
    reason: Optional[str] = None
    new_product_id: Optional[int] = None


@router.post("/services/{service_id}/module/{action}")
async def whmcs_module_action(
    service_id: int,
    action: str,
    payload: ModuleActionPayload,
    current_user: UserResponse = Depends(get_current_user),
):
    """Admin-only module actions: create | suspend | unsuspend | terminate | changepackage."""
    _require_admin(current_user)
    if payload.service_id != service_id:
        raise HTTPException(status_code=400, detail="Path service_id does not match body.")

    normalized = action.lower()
    action_map = {
        "create": "ModuleCreate",
        "suspend": "ModuleSuspend",
        "unsuspend": "ModuleUnsuspend",
        "terminate": "ModuleTerminate",
        "changepackage": "ModuleChangePackage",
    }
    whmcs_action = action_map.get(normalized)
    if not whmcs_action:
        raise HTTPException(status_code=400, detail=f"Unsupported module action: {action}")

    params: Dict[str, Any] = {"serviceid": service_id}
    if normalized == "suspend" and payload.reason:
        params["suspendreason"] = payload.reason
    if normalized == "changepackage" and payload.new_product_id:
        params["newproductid"] = payload.new_product_id

    body = await call_whmcs(whmcs_action, params)
    return {"result": body.get("result"), "message": body.get("message")}


# ---------- Client service upgrade/downgrade ----------
class UpgradeServicePayload(BaseModel):
    service_id: int
    new_product_id: int


@router.post("/services/{service_id}/upgrade")
async def whmcs_upgrade_service(
    service_id: int,
    payload: UpgradeServicePayload,
    current_user: UserResponse = Depends(get_current_user),
):
    """User-facing product upgrade/downgrade — scoped to the caller's services."""
    if payload.service_id != service_id:
        raise HTTPException(status_code=400, detail="Path service_id does not match body.")

    client_id = await _require_client_id(current_user.email)
    owned = await call_whmcs_safe(
        "GetClientsProducts", {"clientid": client_id, "limitnum": 500}
    )
    products = []
    if owned:
        raw = owned.get("products")
        if isinstance(raw, dict):
            raw = raw.get("product", [])
        if isinstance(raw, list):
            products = raw
    ids = {int(p.get("id")) for p in products if p.get("id") is not None}
    if int(service_id) not in ids:
        raise HTTPException(status_code=403, detail="Service does not belong to this user.")

    body = await call_whmcs(
        "UpdateClientProduct",
        {"serviceid": service_id, "pid": payload.new_product_id},
    )
    return {"result": body.get("result"), "message": body.get("message")}