from __future__ import annotations

import hashlib
import json
import logging
import secrets
from base64 import urlsafe_b64encode
from urllib.parse import urlencode

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy import func, select, desc
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db, require_permission
from app.config import settings
from app.core.services.plugin_helper import require_plugin
from app.plugins.xero_import.client import XeroClient
from app.plugins.xero_import.models import XeroConnection, XeroSyncLog, XeroSyncState
from app.plugins.xero_import.schemas import XeroAuthUrlOut, XeroConnectionStatus, XeroSyncLogOut, XeroSyncLogPage, XeroSyncResult
from app.plugins.xero_import.services import XeroImportService

logger = logging.getLogger("uvicorn.error")

router = APIRouter(prefix="/xero-import", tags=["Xero 数据同步"])

XERO_AUTHORIZE_URL = "https://login.xero.com/identity/connect/authorize"
XERO_SCOPES = "offline_access openid profile email accounting.contacts accounting.settings accounting.invoices accounting.payments"

def _generate_pkce() -> tuple[str, str]:
    verifier = secrets.token_urlsafe(64)
    challenge = urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
    return verifier, challenge


async def _save_pkce(db: AsyncSession, state: str, verifier: str):
    from sqlalchemy import text as sql_text
    await db.execute(sql_text(
        "INSERT INTO xero_pkce_store (state_key, code_verifier) VALUES (:s, :v) "
        "ON DUPLICATE KEY UPDATE code_verifier = :v2"
    ), {"s": state, "v": verifier, "v2": verifier})
    await db.commit()


async def _pop_pkce(db: AsyncSession, state: str) -> str | None:
    from sqlalchemy import text as sql_text
    row = await db.execute(sql_text(
        "SELECT code_verifier FROM xero_pkce_store WHERE state_key = :s LIMIT 1"
    ), {"s": state})
    result = row.scalar_one_or_none()
    if result:
        await db.execute(sql_text("DELETE FROM xero_pkce_store WHERE state_key = :s"), {"s": state})
        await db.commit()
    return result


def _get_plugin_config(db_config_row) -> dict:
    if not db_config_row:
        return {}
    cfg = db_config_row
    if isinstance(cfg, str):
        cfg = json.loads(cfg)
    return cfg if isinstance(cfg, dict) else {}


async def _load_config(db: AsyncSession, tenant_id: int) -> dict:
    from sqlalchemy import text
    row = await db.execute(
        text("SELECT config FROM plugin_configs WHERE tenant_id=:tid AND plugin_name='xero_import' AND is_active=1 LIMIT 1"),
        {"tid": tenant_id},
    )
    r = row.fetchone()
    if not r:
        return {}
    return _get_plugin_config(r[0])


def _build_client(cfg: dict) -> XeroClient:
    client_id = cfg.get("client_id")
    client_secret = cfg.get("client_secret")
    redirect_uri = cfg.get("redirect_uri")
    if not client_id or not client_secret or not redirect_uri:
        raise HTTPException(status_code=400, detail="请先在插件配置中填写 Xero Client ID、Client Secret 和 Redirect URI")
    return XeroClient(client_id, client_secret, redirect_uri)


def _parse_json_dict(raw) -> dict:
    if isinstance(raw, str):
        return json.loads(raw) if raw else {}
    if isinstance(raw, dict):
        return raw
    return {}


def _build_sync_options(cfg: dict) -> dict:
    return {
        "sync_price": cfg.get("sync_price", "false") == "true",
        "auto_delist_zero_stock": cfg.get("auto_delist_zero_stock", "true") == "true",
        "tax_type_mapping": _parse_json_dict(cfg.get("tax_type_mapping")),
        "country_name_mapping": _parse_json_dict(cfg.get("country_name_mapping")),
        "sync_address_overwrite": cfg.get("sync_address_overwrite", "false") == "true",
    }


@router.get("/status", response_model=XeroConnectionStatus, summary="获取 Xero 连接状态")
async def get_status(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)

    result = await db.execute(
        select(XeroConnection).where(
            XeroConnection.tenant_id == tid,
            XeroConnection.is_active == 1,
        ).limit(1)
    )
    conn = result.scalar_one_or_none()
    if not conn:
        return XeroConnectionStatus(connected=False)

    # 获取同步状态
    contacts_state = await db.execute(
        select(XeroSyncState.last_success_at).where(
            XeroSyncState.tenant_id == tid,
            XeroSyncState.connection_id == conn.id,
            XeroSyncState.resource == "contacts",
        )
    )
    items_state = await db.execute(
        select(XeroSyncState.last_success_at).where(
            XeroSyncState.tenant_id == tid,
            XeroSyncState.connection_id == conn.id,
            XeroSyncState.resource == "items",
        )
    )

    return XeroConnectionStatus(
        connected=True,
        xero_tenant_id=conn.xero_tenant_id,
        tenant_name=conn.tenant_name,
        expires_at=conn.expires_at,
        contacts_last_sync=contacts_state.scalar_one_or_none(),
        items_last_sync=items_state.scalar_one_or_none(),
    )


@router.get("/auth-url", response_model=XeroAuthUrlOut, summary="获取 Xero OAuth 授权 URL")
async def get_auth_url(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)
    cfg = await _load_config(db, tid)
    client = _build_client(cfg)

    verifier, challenge = _generate_pkce()
    state = str(tid)
    await _save_pkce(db, state, verifier)

    params = {
        "response_type": "code",
        "client_id": client.client_id,
        "redirect_uri": client.redirect_uri,
        "scope": XERO_SCOPES,
        "state": state,
    }

    # 尝试 PKCE（部分 Xero App 强制要求）
    params["code_challenge"] = challenge
    params["code_challenge_method"] = "S256"

    url = f"{XERO_AUTHORIZE_URL}?{urlencode(params)}"
    logger.info("[xero] auth-url generated: %s", url)
    return XeroAuthUrlOut(url=url)


@router.get("/callback", summary="Xero OAuth 回调")
async def oauth_callback(
    code: str = Query(...),
    state: str = Query(""),
    db: AsyncSession = Depends(get_db),
):
    tid = int(state) if state.isdigit() else settings.DEFAULT_TENANT_ID
    await require_plugin("xero_import", db, tid)
    cfg = await _load_config(db, tid)
    client = _build_client(cfg)

    code_verifier = await _pop_pkce(db, state)
    token_data = await client.exchange_code(code, code_verifier=code_verifier)
    access_token = token_data["access_token"]

    connections = await client.connections(access_token)
    if not connections:
        raise HTTPException(status_code=400, detail="未找到 Xero 组织，请确认授权了正确的账户")

    xero_org = connections[0]
    xero_tenant_id = xero_org["tenantId"]
    org_name = xero_org.get("tenantName", "")

    # 停用其他连接（不同 xero_tenant_id 的）
    old_result = await db.execute(
        select(XeroConnection).where(
            XeroConnection.tenant_id == tid,
            XeroConnection.is_active == 1,
            XeroConnection.xero_tenant_id != xero_tenant_id,
        )
    )
    for old_conn in old_result.scalars().all():
        old_conn.is_active = 0

    # 查找同一 (tenant_id, xero_tenant_id) 的已有记录，复用更新
    existing_result = await db.execute(
        select(XeroConnection).where(
            XeroConnection.tenant_id == tid,
            XeroConnection.xero_tenant_id == xero_tenant_id,
        ).limit(1)
    )
    conn = existing_result.scalar_one_or_none()

    if conn:
        conn.tenant_name = org_name
        conn.access_token = access_token
        conn.refresh_token = token_data.get("refresh_token", "")
        conn.expires_at = client.expires_at_from_token(token_data)
        conn.scopes = XERO_SCOPES
        conn.is_active = 1
    else:
        conn = XeroConnection(
            tenant_id=tid,
            xero_tenant_id=xero_tenant_id,
            tenant_name=org_name,
            access_token=access_token,
            refresh_token=token_data.get("refresh_token", ""),
            expires_at=client.expires_at_from_token(token_data),
            scopes=XERO_SCOPES,
            is_active=1,
        )
        db.add(conn)

    await db.commit()
    return {"ok": True, "tenant_name": org_name}


@router.post("/disconnect", summary="断开 Xero 连接")
async def disconnect(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)

    result = await db.execute(
        select(XeroConnection).where(
            XeroConnection.tenant_id == tid,
            XeroConnection.is_active == 1,
        )
    )
    for conn in result.scalars().all():
        conn.is_active = 0
    await db.commit()
    return {"ok": True}


@router.post("/sync/contacts", response_model=XeroSyncResult, summary="手动同步客户")
async def sync_contacts(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)
    cfg = await _load_config(db, tid)
    client = _build_client(cfg)
    sync_options = _build_sync_options(cfg)
    svc = XeroImportService(db, tid, client, sync_options=sync_options)
    result = await svc.sync_contacts(mode="manual")
    return XeroSyncResult(**result)


@router.post("/sync/items", response_model=XeroSyncResult, summary="手动同步商品/库存")
async def sync_items(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)
    cfg = await _load_config(db, tid)
    client = _build_client(cfg)
    sync_options = _build_sync_options(cfg)
    svc = XeroImportService(db, tid, client, sync_options=sync_options)
    logger.info("[xero_import] POST /sync/items: 开始手动同步商品, options=%s", sync_options)
    try:
        result = await svc.sync_items(mode="manual")
        logger.info("[xero_import] POST /sync/items: 完成 result=%s", result)
        return XeroSyncResult(**result)
    except Exception as e:
        logger.error("[xero_import] POST /sync/items: 异常 %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail=str(e)[:500])


@router.post("/push-order/{order_id}", summary="手动推送单个订单到 Xero")
async def push_order(
    order_id: int,
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)
    cfg = await _load_config(db, tid)
    client = _build_client(cfg)
    sync_options = _build_sync_options(cfg)
    svc = XeroImportService(db, tid, client, sync_options=sync_options)
    result = await svc.push_order_to_xero(order_id)
    if not result.get("success"):
        raise HTTPException(status_code=400, detail=result.get("error", "推送失败"))
    await db.commit()
    return result


@router.post("/push-orders", response_model=XeroSyncResult, summary="批量推送待推订单到 Xero")
async def push_orders(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)
    cfg = await _load_config(db, tid)
    client = _build_client(cfg)
    sync_options = _build_sync_options(cfg)
    trigger_status = cfg.get("push_order_status", "paid")
    svc = XeroImportService(db, tid, client, sync_options=sync_options)
    result = await svc.push_pending_orders(trigger_status, mode="manual")
    return XeroSyncResult(**result)


@router.get("/push-order-stats", summary="获取待推送订单统计")
async def push_order_stats(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)
    cfg = await _load_config(db, tid)
    trigger_status = cfg.get("push_order_status", "paid")

    from sqlalchemy import text as sql_text
    # 统计未推送订单数
    row = await db.execute(sql_text(
        "SELECT COUNT(*) FROM orders o "
        "WHERE o.tenant_id = :tid AND o.status = :st "
        "AND o.id NOT IN ("
        "  SELECT local_id FROM xero_entity_maps "
        "  WHERE tenant_id = :tid AND entity_type = 'invoice'"
        ")"
    ), {"tid": tid, "st": trigger_status})
    pending_count = row.scalar() or 0

    # 已推送数
    pushed_row = await db.execute(sql_text(
        "SELECT COUNT(*) FROM xero_entity_maps "
        "WHERE tenant_id = :tid AND entity_type = 'invoice'"
    ), {"tid": tid})
    pushed_count = pushed_row.scalar() or 0

    return {
        "trigger_status": trigger_status,
        "pending_count": pending_count,
        "pushed_count": pushed_count,
    }


@router.get("/xero-tax-types", summary="获取 Xero 端的税率代码列表")
async def get_xero_tax_types(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)
    cfg = await _load_config(db, tid)
    client = _build_client(cfg)

    conn_result = await db.execute(
        select(XeroConnection).where(
            XeroConnection.tenant_id == tid,
            XeroConnection.is_active == 1,
        ).limit(1)
    )
    conn = conn_result.scalar_one_or_none()
    if not conn:
        raise HTTPException(status_code=400, detail="请先连接 Xero")

    from datetime import datetime as _dt, timezone as _tz
    access_token = conn.access_token
    expires = conn.expires_at.replace(tzinfo=_tz.utc) if conn.expires_at.tzinfo is None else conn.expires_at
    if expires < _dt.now(_tz.utc):
        svc = XeroImportService(db, tid, client)
        access_token = await svc._ensure_token(conn)

    raw_rates = await client.tax_rates(access_token, conn.xero_tenant_id)
    return [
        {
            "tax_type": r.get("TaxType", ""),
            "name": r.get("Name", ""),
            "rate": r.get("EffectiveRate", r.get("DisplayTaxRate", "")),
            "status": r.get("Status", ""),
        }
        for r in raw_rates
        if r.get("Status") == "ACTIVE" and r.get("CanApplyToRevenue")
    ]


@router.get("/local-tax-classes", summary="获取本地税种列表")
async def get_local_tax_classes(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    from app.plugins.tax.models import TaxClass, TaxRate
    result = await db.execute(
        select(TaxClass).where(TaxClass.tenant_id == tid).order_by(TaxClass.sort_order)
    )
    classes = result.scalars().all()

    out = []
    for tc in classes:
        rates_result = await db.execute(
            select(TaxRate).where(TaxRate.tax_class_id == tc.id)
        )
        rates = rates_result.scalars().all()
        rate_str = ", ".join(f"{r.name} {float(r.rate)*100:.1f}%" for r in rates) if rates else "无税率"
        out.append({
            "id": tc.id,
            "name": tc.name,
            "is_default": bool(tc.is_default),
            "rates": rate_str,
        })
    return out


@router.get("/xero-countries", summary="获取已同步客户中出现过的 Xero 国家名")
async def get_xero_countries(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    from app.core.models.customer import Customer

    result = await db.execute(
        select(Customer.extra_data).where(
            Customer.tenant_id == tid,
            Customer.extra_data.isnot(None),
        )
    )
    names: set[str] = set()
    for (extra,) in result.all():
        if not isinstance(extra, dict):
            continue
        raw = (extra.get("xero") or {}).get("raw") or {}
        for addr in raw.get("Addresses") or []:
            country = (addr.get("Country") or "").strip()
            if country:
                names.add(country)
    return sorted(names)


@router.get("/local-countries", summary="获取本地国家列表")
async def get_local_countries(
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    from app.core.models.country import Country
    result = await db.execute(
        select(Country).where(Country.is_active == 1).order_by(Country.sort_order)
    )
    return [
        {"code": c.code, "name": f"{c.name_zh}（{c.name_en}）"}
        for c in result.scalars().all()
    ]


@router.get("/logs", response_model=XeroSyncLogPage, summary="获取同步日志（分页）")
async def get_logs(
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    db: AsyncSession = Depends(get_db),
    _user=Depends(require_permission("plugins.configure")),
):
    tid = _user.tenant_id
    await require_plugin("xero_import", db, tid)

    base = select(XeroSyncLog).where(XeroSyncLog.tenant_id == tid)

    total_result = await db.execute(select(func.count()).select_from(base.subquery()))
    total = total_result.scalar() or 0

    result = await db.execute(
        base.order_by(desc(XeroSyncLog.created_at))
        .offset((page - 1) * page_size)
        .limit(page_size)
    )
    return XeroSyncLogPage(
        items=[XeroSyncLogOut.model_validate(r) for r in result.scalars().all()],
        total=total,
        page=page,
        page_size=page_size,
    )
