"""高级运费规则 Admin API。挂载于 /api/admin。

── 权限 ────────────────────────────────────────────────────────────────────
读/列表          plugins.advanced_shipping.view
一切写入 + 模拟  plugins.advanced_shipping.configure
修订回滚         configure + rollback
AI 应用（Task 7）configure + ai_apply       ← 依赖在本模块导出，端点由 Task 7 添加

整个 router 由 require_plugin("advanced_shipping_rules", db) 兜底：插件未启用一律 503。

路由体只做一件事——调用 admin_service 的一个函数。逻辑都在 admin_service 里，
那里能在没有 DB/TestClient 的环境下被真实地测。
"""
from decimal import Decimal
from typing import Any, Literal
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel, Field, field_validator
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_db, require_permission
from app.core.models.user import User
from app.core.services.plugin_helper import require_plugin

from . import admin_service as svc
from . import ai as ai_svc
from .schemas import AdvancedShippingRuleIn

_VIEW = require_permission("plugins.advanced_shipping.view")
_CONFIGURE = require_permission("plugins.advanced_shipping.configure")
_ROLLBACK = require_permission("plugins.advanced_shipping.rollback")
#: Task 7 的 AI 应用端点用：user=Depends(_CONFIGURE), _ai=Depends(_AI_APPLY)（两个都要过）
_AI_APPLY = require_permission("plugins.advanced_shipping.ai_apply")


async def _require_plugin(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
) -> None:
    await require_plugin("advanced_shipping_rules", db, current_user.tenant_id)


router = APIRouter(
    prefix="/advanced-shipping",
    tags=["advanced_shipping_rules"],
    dependencies=[Depends(_require_plugin)],
)


# ── 请求体 ──────────────────────────────────────────────────────────────────

class SimulationItemIn(BaseModel):
    product_id: int
    variant_id: int | None = None
    qty: Decimal = Field(..., gt=0, description="可售单位件数（不是基础单位、不是包裹数）")
    line_total: Decimal = Field(..., ge=0, description="该行金额；必填，金额不允许默认成 0")


class SimulationIn(BaseModel):
    """模拟只收购物车事实。**没有 fee 字段**——运费永远由服务端算。"""
    items: list[SimulationItemIn] = Field(default_factory=list)
    cart_subtotal: Decimal = Field(..., ge=0)
    cart_total: Decimal = Field(..., ge=0)
    address: dict[str, Any] | None = Field(None, description="recv_country/recv_province/... 留空即无地址场景")
    delivery_mode: str = "standard"
    payment_method: str | None = None
    coupon_codes: list[str] = Field(default_factory=list)
    customer_group_ids: list[int] = Field(default_factory=list)


class SimulationAckIn(SimulationIn):
    kind: Literal["no_address", "representative"]


class RuleWriteIn(AdvancedShippingRuleIn):
    change_note: str | None = Field(None, max_length=500)


class SettingsUpdateIn(BaseModel):
    """cache_version 不在这里——版本号只由 SQL 自增。

    operating_timezone 可缺省：只改 mode 的 PUT 不该顺手把租户时区重置成默认值。
    """
    mode: str = Field(..., pattern="^(shadow|enforced)$")
    operating_timezone: str | None = Field(None, max_length=64)

    @field_validator("operating_timezone")
    @classmethod
    def _validate_iana_timezone(cls, v: str | None) -> str | None:
        if v:
            try:
                ZoneInfo(v)
            except ZoneInfoNotFoundError as exc:
                raise ValueError(f"不是合法的 IANA 时区: {v!r}") from exc
        return v


# ── 规则 CRUD ───────────────────────────────────────────────────────────────

@router.get("/rules")
async def list_rules(db: AsyncSession = Depends(get_db), user: User = Depends(_VIEW)):
    return await svc.list_rules(db, user.tenant_id)


@router.post("/rules", status_code=201)
async def create_rule(body: RuleWriteIn, db: AsyncSession = Depends(get_db),
                      user: User = Depends(_CONFIGURE)):
    return await svc.create_rule(db, user.tenant_id, body, user_id=user.id,
                                 note=body.change_note)


@router.put("/rules/{rule_id}")
async def update_rule(rule_id: int, body: RuleWriteIn, db: AsyncSession = Depends(get_db),
                      user: User = Depends(_CONFIGURE)):
    return await svc.update_rule(db, user.tenant_id, rule_id, body, user_id=user.id,
                                note=body.change_note)


@router.delete("/rules/{rule_id}")
async def delete_rule(rule_id: int, db: AsyncSession = Depends(get_db),
                      user: User = Depends(_CONFIGURE)):
    return await svc.delete_rule(db, user.tenant_id, rule_id, user_id=user.id)


# ── 修订与回滚 ──────────────────────────────────────────────────────────────

@router.get("/rules/{rule_id}/revisions")
async def list_revisions(rule_id: int,
                         limit: int = Query(50, ge=1, le=200),
                         offset: int = Query(0, ge=0),
                         db: AsyncSession = Depends(get_db),
                         user: User = Depends(_VIEW)):
    return await svc.list_revisions(db, user.tenant_id, rule_id, limit=limit, offset=offset)


@router.post("/rules/{rule_id}/revisions/{revision_no}/rollback")
async def rollback_rule(rule_id: int, revision_no: int, db: AsyncSession = Depends(get_db),
                        user: User = Depends(_CONFIGURE), _rb: User = Depends(_ROLLBACK)):
    """回滚到指定修订：写一条新修订，历史修订只增不改。

    **隐藏边不回滚**（快照不含 hide_rule_ids，另一端可能已被 CASCADE 删除），
    from_legacy_conversion 标记也不受影响。隐藏边请单独调整。
    """
    return await svc.rollback_rule(db, user.tenant_id, rule_id, revision_no, user_id=user.id)


# ── 配置与模式 ──────────────────────────────────────────────────────────────

@router.get("/settings")
async def get_settings(db: AsyncSession = Depends(get_db), user: User = Depends(_VIEW)):
    return await svc.read_settings(db, user.tenant_id)


@router.put("/settings")
async def put_settings(body: SettingsUpdateIn, db: AsyncSession = Depends(get_db),
                       user: User = Depends(_CONFIGURE)):
    return await svc.write_settings(db, user.tenant_id, body)


# ── 影子事件（只读，没有 update/delete 端点）──────────────────────────────────

@router.get("/shadow-events")
async def list_shadow_events(limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0),
                             db: AsyncSession = Depends(get_db), user: User = Depends(_VIEW)):
    return await svc.list_shadow_events(db, user.tenant_id, limit=limit, offset=offset)


# ── 模板（随代码发布，只读）──────────────────────────────────────────────────

@router.get("/templates")
async def list_templates(user: User = Depends(_VIEW)):
    return svc.list_templates()


# ── 模拟 ────────────────────────────────────────────────────────────────────

@router.post("/simulate")
async def simulate(body: SimulationIn, db: AsyncSession = Depends(get_db),
                   user: User = Depends(_CONFIGURE)):
    return await svc.simulate(db, user.tenant_id, body)


@router.post("/simulate/acknowledge")
async def acknowledge_simulation(body: SimulationAckIn, db: AsyncSession = Depends(get_db),
                                 user: User = Depends(_CONFIGURE)):
    return await svc.acknowledge_simulation(db, user.tenant_id, body, user_id=user.id)


# ── AI 起草与显式审核应用 ────────────────────────────────────────────────────

class AiDraftIn(BaseModel):
    """起草输入。table_text 是**粘贴的** CSV/TSV，不是文件上传——本任务不做 XLSX 解析。"""
    prompt: str = Field("", max_length=ai_svc.MAX_PROMPT_CHARS)
    table_text: str | None = Field(
        None, description=f"粘贴的 CSV/TSV，最大 {ai_svc.MAX_TABLE_BYTES} 字节")


class AiReviseIn(BaseModel):
    rules: list[dict] = Field(..., min_length=1, max_length=ai_svc.MAX_AI_RULES_PER_DRAFT)
    instruction: str = Field(..., min_length=1, max_length=ai_svc.MAX_PROMPT_CHARS)
    sample_cases: list[ai_svc.AiSampleCase] = Field(default_factory=list,
                                                    max_length=ai_svc.MAX_SAMPLE_CASES)


class AiApplyIn(BaseModel):
    """应用输入。**刻意没有 mode，也没有插件启用字段**：AI 路径永远碰不到强制模式和插件开关。

    rules 收 dict 而不是已解析的模型：应用要在服务层**重新**过一遍
    AdvancedShippingRuleIn，这样"跳过校验"这件事必然被测试抓到。
    """
    rules: list[dict] = Field(..., min_length=1, max_length=ai_svc.MAX_AI_RULES_PER_DRAFT)
    change_note: str | None = Field(None, max_length=500)
    sample_cases: list[ai_svc.AiSampleCase] = Field(default_factory=list,
                                                    max_length=ai_svc.MAX_SAMPLE_CASES)


@router.post("/ai-draft")
async def ai_draft(body: AiDraftIn, db: AsyncSession = Depends(get_db),
                   user: User = Depends(_CONFIGURE)):
    """AI 起草：只读租户事实、跑模拟、返回待审核草稿。**不写任何配置**。"""
    return await ai_svc.ai_draft(db, user.tenant_id, body)


@router.post("/ai-revise")
async def ai_revise(body: AiReviseIn, db: AsyncSession = Depends(get_db),
                    user: User = Depends(_CONFIGURE)):
    """按当前内存草稿与下一句指令生成替代草稿；不写运费配置。"""
    return await ai_svc.ai_revise(db, user.tenant_id, body)


@router.post("/ai-apply", status_code=201)
async def ai_apply(body: AiApplyIn, db: AsyncSession = Depends(get_db),
                   user: User = Depends(_CONFIGURE), _ai: User = Depends(_AI_APPLY)):
    """应用**操作者审核过的**草稿：重新校验 → 走既有写路径 → 回一份写入后的模拟。

    需要 configure **与** ai_apply 两个权限：能改运费的人未必被授权让 AI 代写。
    """
    return await ai_svc.ai_apply(db, user.tenant_id, body, user_id=user.id)


# ── legacy 转换（显式两步；绝不是插件启用的副作用）───────────────────────────

@router.get("/legacy-conversion/preview")   # 纯只读：GET 才能被前端自由重取
async def preview_legacy_conversion(db: AsyncSession = Depends(get_db),
                                    user: User = Depends(_VIEW)):
    return await svc.preview_legacy_conversion(db, user.tenant_id)


@router.post("/legacy-conversion/apply")
async def apply_legacy_conversion(db: AsyncSession = Depends(get_db),
                                  user: User = Depends(_CONFIGURE)):
    return await svc.apply_legacy_conversion(db, user.tenant_id, user_id=user.id)
