from __future__ import annotations

import hashlib
import json
import logging
from datetime import datetime, timezone
from urllib.parse import parse_qsl

from fastapi import APIRouter, Depends, Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.models.instance import Instance, InstanceStatus
from app.services.provision_service import (
    _apply_zjmf_host_detail,
    _int_or_none,
    _merge_upstream_payload,
    _stable_zjmf_downstream_token,
)

router = APIRouter()
log = logging.getLogger("qycw.zjmf_downstream")


async def _payload_from_request(request: Request) -> dict[str, object]:
    content_type = request.headers.get("content-type", "").lower()
    if "application/json" in content_type:
        data = await request.json()
        return data if isinstance(data, dict) else {}

    if "multipart/form-data" in content_type:
        form = await request.form()
        return {key: value for key, value in form.multi_items()}

    body = await request.body()
    if not body:
        return {}
    text = body.decode("utf-8", errors="ignore")
    return {key: value for key, value in parse_qsl(text, keep_blank_values=True)}


def _php_json_md5(data: dict[str, object]) -> str:
    ordered = {key: data[key] for key in sorted(data.keys())}
    raw = json.dumps(ordered, ensure_ascii=False, separators=(",", ":"))
    return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()


def _valid_signature(payload: dict[str, object], token: str) -> bool:
    signature = str(payload.get("signature") or "").strip().upper()
    rand_str = str(payload.get("rand_str") or "").strip()
    raw_id = payload.get("id")
    if not signature or not rand_str or raw_id in (None, ""):
        return False

    candidates: list[object] = [str(raw_id)]
    int_id = _int_or_none(raw_id)
    if int_id is not None:
        candidates.append(int_id)

    for candidate_id in candidates:
        expected = _php_json_md5({"id": candidate_id, "rand_str": rand_str, "token": token})
        if expected == signature:
            return True
    return False


def _status_from_domainstatus(value: object) -> InstanceStatus | None:
    status = str(value or "").strip().lower()
    if status == "active":
        return InstanceStatus.ACTIVE
    if status == "suspended":
        return InstanceStatus.SUSPENDED
    if status in {"deleted", "cancelled"}:
        return InstanceStatus.TERMINATED
    if status in {"fraud", "failed"}:
        return InstanceStatus.PROVISION_FAILED
    return None


def _sanitize_payload(payload: dict[str, object]) -> dict[str, object]:
    hidden = {"password", "rootpassword", "ippassword"}
    return {key: value for key, value in payload.items() if key not in hidden}


@router.post("/host/sync")
async def sync_host(request: Request, db: AsyncSession = Depends(get_db)):
    payload = await _payload_from_request(request)
    instance_id = _int_or_none(payload.get("id"))
    if instance_id is None:
        return {"status": 400, "msg": "invalid downstream id"}

    result = await db.execute(select(Instance).where(Instance.id == instance_id))
    instance = result.scalar_one_or_none()
    if not instance:
        log.warning("zjmf downstream host sync rejected: instance=%s not found", instance_id)
        return {"status": 400, "msg": "instance not found"}

    downstream_token = instance.downstream_token or _stable_zjmf_downstream_token(instance)
    if not _valid_signature(payload, downstream_token):
        log.warning("zjmf downstream host sync rejected: invalid signature instance=%s", instance_id)
        return {"status": 400, "msg": "signature invalid"}
    if not instance.downstream_token:
        instance.downstream_token = downstream_token

    upstream_host_id = _int_or_none(payload.get("host_id"))
    if upstream_host_id is not None:
        instance.upstream_id = upstream_host_id

    _apply_zjmf_host_detail(instance, payload)

    mapped_status = _status_from_domainstatus(payload.get("domainstatus"))
    if mapped_status is not None:
        instance.status = mapped_status

    if mapped_status == InstanceStatus.ACTIVE and upstream_host_id:
        instance.provision_log = f"zjmf downstream sync active host_id={upstream_host_id}"
    elif mapped_status is not None:
        instance.provision_log = f"zjmf downstream sync status={mapped_status.value}"

    stored_payload = _sanitize_payload(payload)
    stored_payload["received_at"] = datetime.now(timezone.utc).isoformat()
    _merge_upstream_payload(instance, "zjmf_downstream_host_sync", stored_payload)
    await db.flush()
    await db.commit()

    return {"status": 200, "msg": "sync success"}
