"""管理后台 — 企业级运费系统 API

路由前缀：/api/shipping
资源：
  carriers   - 快递公司
  zones      - 运费区域
  methods    - 运费方案（含阶梯）
  surcharges - 附加费
"""
from decimal import Decimal
from typing import Optional, List
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy import select, delete
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.api.deps import get_db, get_admin_user
from app.core.models.shipping_carrier import ShippingCarrier
from app.core.models.shipping_zone import ShippingZone
from app.core.models.shipping import ShippingMethod
from app.core.models.shipping_rate_tier import ShippingRateTier
from app.core.models.shipping_surcharge import ShippingSurcharge
from app.core.models.country import Country, CountryProvince
from app.core.models.user import User

router = APIRouter(prefix="/shipping", tags=["运费管理"])


# ══════════════════════════════════════════════════════════════
#  国家/省份主数据（供区域编辑器选择）
# ══════════════════════════════════════════════════════════════

class CountryProvinceOut(BaseModel):
    code: str
    name_zh: str
    name_en: str
    provinces: List[str] = []
    model_config = {"from_attributes": True}


@router.get("/available-countries", response_model=List[CountryProvinceOut])
async def list_available_countries(
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    """返回系统内所有激活的国家及其省份列表，供运费区域编辑器使用"""
    result = await db.execute(
        select(Country)
        .where(Country.is_active == 1)
        .options(selectinload(Country.provinces))
        .order_by(Country.sort_order)
    )
    countries = result.scalars().all()
    return [
        CountryProvinceOut(
            code=c.code,
            name_zh=c.name_zh,
            name_en=c.name_en,
            provinces=[p.name for p in sorted(c.provinces, key=lambda x: x.sort_order)],
        )
        for c in countries
    ]


# ══════════════════════════════════════════════════════════════
#  Schemas
# ══════════════════════════════════════════════════════════════

# ── Carrier ──────────────────────────────────────────────────

class CarrierIn(BaseModel):
    name: str = Field(..., max_length=100)
    code: str = Field(..., max_length=50, description="唯一标识，如 dhl / nz_post")
    logo_url: Optional[str] = None
    tracking_url_template: Optional[str] = Field(None, description="追踪链接模板，用 {tracking_no} 占位")
    description: Optional[str] = None
    api_config: Optional[dict] = None
    sort_order: int = 0
    is_active: bool = True


class CarrierOut(BaseModel):
    id: int
    name: str
    code: str
    logo_url: Optional[str]
    tracking_url_template: Optional[str]
    description: Optional[str]
    sort_order: int
    is_active: bool
    created_at: datetime
    updated_at: datetime
    model_config = {"from_attributes": True}


# ── Zone ─────────────────────────────────────────────────────

class ZoneIn(BaseModel):
    name: str = Field(..., max_length=100)
    description: Optional[str] = None
    countries: Optional[List[str]] = Field(None, description='ISO 3166-1 alpha-2 列表，如 ["NZ","AU"]')
    province_rules: Optional[dict] = Field(None, description='{"NZ":["Auckland","Wellington"]}')
    is_all_countries: bool = Field(False, description="兜底区域，匹配所有地址")
    sort_order: int = Field(0, description="匹配优先级，越小越先")
    is_active: bool = True


class ZoneOut(BaseModel):
    id: int
    name: str
    description: Optional[str]
    countries: Optional[List[str]]
    province_rules: Optional[dict]
    is_all_countries: bool
    sort_order: int
    is_active: bool
    created_at: datetime
    updated_at: datetime
    model_config = {"from_attributes": True}


# ── Rate Tier ─────────────────────────────────────────────────

class RateTierIn(BaseModel):
    min_value: Decimal = Field(Decimal("0"), ge=0)
    max_value: Optional[Decimal] = Field(None, description="None=无上限")
    fee: Decimal = Field(Decimal("0"), ge=0)
    is_free: bool = False
    sort_order: int = 0


class RateTierOut(RateTierIn):
    id: int
    model_config = {"from_attributes": True}


# ── Method ────────────────────────────────────────────────────

PRICING_METHODS = {"flat", "weight", "dimensional_weight", "per_item", "order_value_tier"}


class MethodIn(BaseModel):
    name: str = Field(..., max_length=100)
    description: Optional[str] = None
    carrier_id: Optional[int] = None
    zone_id: Optional[int] = None
    pricing_method: str = Field("flat", description="flat/weight/dimensional_weight/per_item/order_value_tier")
    base_fee: Decimal = Field(Decimal("0"), ge=0, description="基础费用")
    unit_fee: Decimal = Field(Decimal("0"), ge=0, description="续加单价")
    first_weight: Decimal = Field(Decimal("1"), gt=0, description="首重重量（kg）")
    unit_step: Decimal = Field(Decimal("1"), gt=0, description="续重步长（kg/件）")
    free_threshold: Optional[Decimal] = Field(None, ge=0, description="满额免运费门槛")
    min_order_amount: Optional[Decimal] = Field(None, ge=0, description="最低起运金额")
    max_weight: Optional[Decimal] = Field(None, gt=0, description="最大包裹重量（kg）")
    dimensional_divisor: int = Field(5000, gt=0, description="体积重除数")
    estimated_days: Optional[str] = None
    sort_order: int = 0
    is_active: bool = True
    tiers: List[RateTierIn] = Field(default_factory=list, description="阶梯规则（order_value_tier 时使用）")


class MethodOut(BaseModel):
    id: int
    name: str
    description: Optional[str]
    carrier_id: Optional[int]
    zone_id: Optional[int]
    pricing_method: str
    base_fee: Decimal
    unit_fee: Decimal
    first_weight: Decimal
    unit_step: Decimal
    free_threshold: Optional[Decimal]
    min_order_amount: Optional[Decimal]
    max_weight: Optional[Decimal]
    dimensional_divisor: int
    estimated_days: Optional[str]
    sort_order: int
    is_active: bool
    tiers: List[RateTierOut] = []
    created_at: datetime
    updated_at: datetime
    model_config = {"from_attributes": True}


# ── Surcharge ─────────────────────────────────────────────────

class SurchargeIn(BaseModel):
    method_id: Optional[int] = Field(None, description="None=应用到所有方案")
    name: str = Field(..., max_length=100)
    surcharge_type: str = Field("fixed", description="percentage / fixed")
    value: Decimal = Field(..., ge=0, description="百分比值（5.5=5.5%）或固定金额")
    condition_type: str = Field("always", description="always / zone_match / weight_over")
    condition_zone_ids: Optional[List[int]] = None
    condition_weight_kg: Optional[Decimal] = None
    sort_order: int = 0
    is_active: bool = True


class SurchargeOut(BaseModel):
    id: int
    method_id: Optional[int]
    name: str
    surcharge_type: str
    value: Decimal
    condition_type: str
    condition_zone_ids: Optional[List[int]]
    condition_weight_kg: Optional[Decimal]
    sort_order: int
    is_active: bool
    created_at: datetime
    updated_at: datetime
    model_config = {"from_attributes": True}


# ══════════════════════════════════════════════════════════════
#  快递公司 CRUD
# ══════════════════════════════════════════════════════════════

@router.get("/carriers", response_model=List[CarrierOut])
async def list_carriers(
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    result = await db.execute(
        select(ShippingCarrier)
        .where(ShippingCarrier.tenant_id == user.tenant_id)
        .order_by(ShippingCarrier.sort_order, ShippingCarrier.id)
    )
    return result.scalars().all()


@router.post("/carriers", response_model=CarrierOut, status_code=status.HTTP_201_CREATED)
async def create_carrier(
    body: CarrierIn,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    carrier = ShippingCarrier(
        tenant_id=user.tenant_id,
        **body.model_dump(),
    )
    carrier.is_active = int(body.is_active)
    db.add(carrier)
    await db.commit()
    await db.refresh(carrier)
    return carrier


@router.get("/carriers/{carrier_id}", response_model=CarrierOut)
async def get_carrier(
    carrier_id: int,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    carrier = await _get_carrier_or_404(db, carrier_id, user.tenant_id)
    return carrier


@router.put("/carriers/{carrier_id}", response_model=CarrierOut)
async def update_carrier(
    carrier_id: int,
    body: CarrierIn,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    carrier = await _get_carrier_or_404(db, carrier_id, user.tenant_id)
    data = body.model_dump()
    data["is_active"] = int(body.is_active)
    for k, v in data.items():
        setattr(carrier, k, v)
    await db.commit()
    await db.refresh(carrier)
    return carrier


@router.delete("/carriers/{carrier_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_carrier(
    carrier_id: int,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    carrier = await _get_carrier_or_404(db, carrier_id, user.tenant_id)
    await db.delete(carrier)
    await db.commit()


async def _get_carrier_or_404(db, carrier_id: int, tenant_id: int) -> ShippingCarrier:
    result = await db.execute(
        select(ShippingCarrier)
        .where(ShippingCarrier.id == carrier_id, ShippingCarrier.tenant_id == tenant_id)
    )
    carrier = result.scalar_one_or_none()
    if not carrier:
        raise HTTPException(status_code=404, detail="快递公司不存在")
    return carrier


# ══════════════════════════════════════════════════════════════
#  运费区域 CRUD
# ══════════════════════════════════════════════════════════════

@router.get("/zones", response_model=List[ZoneOut])
async def list_zones(
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    result = await db.execute(
        select(ShippingZone)
        .where(ShippingZone.tenant_id == user.tenant_id)
        .order_by(ShippingZone.sort_order, ShippingZone.id)
    )
    return result.scalars().all()


@router.post("/zones", response_model=ZoneOut, status_code=status.HTTP_201_CREATED)
async def create_zone(
    body: ZoneIn,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    zone = ShippingZone(tenant_id=user.tenant_id, **body.model_dump())
    zone.is_active = int(body.is_active)
    zone.is_all_countries = int(body.is_all_countries)
    db.add(zone)
    await db.commit()
    await db.refresh(zone)
    return zone


@router.get("/zones/{zone_id}", response_model=ZoneOut)
async def get_zone(
    zone_id: int,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    return await _get_zone_or_404(db, zone_id, user.tenant_id)


@router.put("/zones/{zone_id}", response_model=ZoneOut)
async def update_zone(
    zone_id: int,
    body: ZoneIn,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    zone = await _get_zone_or_404(db, zone_id, user.tenant_id)
    data = body.model_dump()
    data["is_active"] = int(body.is_active)
    data["is_all_countries"] = int(body.is_all_countries)
    for k, v in data.items():
        setattr(zone, k, v)
    await db.commit()
    await db.refresh(zone)
    return zone


@router.delete("/zones/{zone_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_zone(
    zone_id: int,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    zone = await _get_zone_or_404(db, zone_id, user.tenant_id)
    # 高级运费规则通过 shipping_method.zone_id 间接依赖本区域：删掉会让 inherit 定价
    # 换一个区域重新匹配（静默算错价），所以有启用中的规则引用时必须拒绝。
    from app.plugins.advanced_shipping_rules.admin_service import zone_reference_names
    names = await zone_reference_names(db, user.tenant_id, zone_id)
    if names:
        raise HTTPException(
            status_code=409,
            detail={"message": "该运费区域仍被高级运费规则引用，无法删除",
                    "rules": names},
        )
    await db.delete(zone)
    await db.commit()


async def _get_zone_or_404(db, zone_id: int, tenant_id: int) -> ShippingZone:
    result = await db.execute(
        select(ShippingZone)
        .where(ShippingZone.id == zone_id, ShippingZone.tenant_id == tenant_id)
    )
    zone = result.scalar_one_or_none()
    if not zone:
        raise HTTPException(status_code=404, detail="运费区域不存在")
    return zone


# ══════════════════════════════════════════════════════════════
#  运费方案 CRUD（含阶梯）
# ══════════════════════════════════════════════════════════════

@router.get("/methods", response_model=List[MethodOut])
async def list_methods(
    zone_id: Optional[int] = None,
    carrier_id: Optional[int] = None,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    q = (
        select(ShippingMethod)
        .where(ShippingMethod.tenant_id == user.tenant_id)
        .options(selectinload(ShippingMethod.tiers))
        .order_by(ShippingMethod.sort_order, ShippingMethod.id)
    )
    if zone_id is not None:
        q = q.where(ShippingMethod.zone_id == zone_id)
    if carrier_id is not None:
        q = q.where(ShippingMethod.carrier_id == carrier_id)
    result = await db.execute(q)
    return result.scalars().all()


@router.post("/methods", response_model=MethodOut, status_code=status.HTTP_201_CREATED)
async def create_method(
    body: MethodIn,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    if body.pricing_method not in PRICING_METHODS:
        raise HTTPException(400, f"pricing_method 必须是 {PRICING_METHODS} 之一")

    tiers_data = body.tiers
    method_data = body.model_dump(exclude={"tiers"})
    method_data["is_active"] = int(body.is_active)
    method_data["tenant_id"] = user.tenant_id

    method = ShippingMethod(**method_data)
    db.add(method)
    await db.flush()  # 获取 method.id

    for tier in tiers_data:
        t = ShippingRateTier(
            method_id=method.id,
            tenant_id=user.tenant_id,
            **tier.model_dump(),
        )
        t.is_free = int(tier.is_free)
        db.add(t)

    await db.commit()
    await db.refresh(method)

    result = await db.execute(
        select(ShippingMethod)
        .where(ShippingMethod.id == method.id)
        .options(selectinload(ShippingMethod.tiers))
    )
    return result.scalar_one()


@router.get("/methods/{method_id}", response_model=MethodOut)
async def get_method(
    method_id: int,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    return await _get_method_or_404(db, method_id, user.tenant_id, with_tiers=True)


@router.put("/methods/{method_id}", response_model=MethodOut)
async def update_method(
    method_id: int,
    body: MethodIn,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    if body.pricing_method not in PRICING_METHODS:
        raise HTTPException(400, f"pricing_method 必须是 {PRICING_METHODS} 之一")

    method = await _get_method_or_404(db, method_id, user.tenant_id, with_tiers=False)

    method_data = body.model_dump(exclude={"tiers"})
    method_data["is_active"] = int(body.is_active)
    for k, v in method_data.items():
        setattr(method, k, v)

    # 替换阶梯：先删旧的，再插新的
    await db.execute(
        delete(ShippingRateTier).where(ShippingRateTier.method_id == method_id)
    )
    for tier in body.tiers:
        t = ShippingRateTier(method_id=method_id, tenant_id=user.tenant_id, **tier.model_dump())
        t.is_free = int(tier.is_free)
        db.add(t)

    await db.commit()

    result = await db.execute(
        select(ShippingMethod)
        .where(ShippingMethod.id == method_id)
        .options(selectinload(ShippingMethod.tiers))
    )
    return result.scalar_one()


@router.delete("/methods/{method_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_method(
    method_id: int,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    method = await _get_method_or_404(db, method_id, user.tenant_id)
    # 高级运费规则直接挂在 shipping_method 上，且那一列没有外键：删掉方案不会连带清理规则，
    # 那些规则会继续报价、inherit 解析到不存在的方案，操作者还改不动（update/rollback 恒 404）。
    from app.plugins.advanced_shipping_rules.admin_service import method_reference_names
    names = await method_reference_names(db, user.tenant_id, method_id)
    if names:
        raise HTTPException(
            status_code=409,
            detail={"message": "该物流方案仍被高级运费规则引用，无法删除",
                    "rules": names},
        )
    await db.delete(method)
    await db.commit()


async def _get_method_or_404(db, method_id: int, tenant_id: int, with_tiers: bool = False) -> ShippingMethod:
    q = select(ShippingMethod).where(
        ShippingMethod.id == method_id,
        ShippingMethod.tenant_id == tenant_id,
    )
    if with_tiers:
        q = q.options(selectinload(ShippingMethod.tiers))
    result = await db.execute(q)
    method = result.scalar_one_or_none()
    if not method:
        raise HTTPException(status_code=404, detail="运费方案不存在")
    return method


# ══════════════════════════════════════════════════════════════
#  附加费 CRUD
# ══════════════════════════════════════════════════════════════

@router.get("/surcharges", response_model=List[SurchargeOut])
async def list_surcharges(
    method_id: Optional[int] = None,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    q = (
        select(ShippingSurcharge)
        .where(ShippingSurcharge.tenant_id == user.tenant_id)
        .order_by(ShippingSurcharge.sort_order, ShippingSurcharge.id)
    )
    if method_id is not None:
        q = q.where(ShippingSurcharge.method_id == method_id)
    result = await db.execute(q)
    return result.scalars().all()


@router.post("/surcharges", response_model=SurchargeOut, status_code=status.HTTP_201_CREATED)
async def create_surcharge(
    body: SurchargeIn,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    _validate_surcharge(body)
    data = body.model_dump()
    data["is_active"] = int(body.is_active)
    data["tenant_id"] = user.tenant_id
    surcharge = ShippingSurcharge(**data)
    db.add(surcharge)
    await db.commit()
    await db.refresh(surcharge)
    return surcharge


@router.put("/surcharges/{surcharge_id}", response_model=SurchargeOut)
async def update_surcharge(
    surcharge_id: int,
    body: SurchargeIn,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    _validate_surcharge(body)
    result = await db.execute(
        select(ShippingSurcharge).where(
            ShippingSurcharge.id == surcharge_id,
            ShippingSurcharge.tenant_id == user.tenant_id,
        )
    )
    surcharge = result.scalar_one_or_none()
    if not surcharge:
        raise HTTPException(404, "附加费不存在")
    data = body.model_dump()
    data["is_active"] = int(body.is_active)
    for k, v in data.items():
        setattr(surcharge, k, v)
    await db.commit()
    await db.refresh(surcharge)
    return surcharge


@router.delete("/surcharges/{surcharge_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_surcharge(
    surcharge_id: int,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    result = await db.execute(
        select(ShippingSurcharge).where(
            ShippingSurcharge.id == surcharge_id,
            ShippingSurcharge.tenant_id == user.tenant_id,
        )
    )
    surcharge = result.scalar_one_or_none()
    if not surcharge:
        raise HTTPException(404, "附加费不存在")
    await db.delete(surcharge)
    await db.commit()


def _validate_surcharge(body: SurchargeIn):
    if body.surcharge_type not in {"percentage", "fixed"}:
        raise HTTPException(400, "surcharge_type 必须是 percentage 或 fixed")
    if body.condition_type not in {"always", "zone_match", "weight_over"}:
        raise HTTPException(400, "condition_type 必须是 always / zone_match / weight_over")
    if body.condition_type == "zone_match" and not body.condition_zone_ids:
        raise HTTPException(400, "zone_match 条件需要提供 condition_zone_ids")
    if body.condition_type == "weight_over" and body.condition_weight_kg is None:
        raise HTTPException(400, "weight_over 条件需要提供 condition_weight_kg")


# ══════════════════════════════════════════════════════════════
#  运费预览（前台调用时计算运费预估）
# ══════════════════════════════════════════════════════════════

class ShippingPreviewIn(BaseModel):
    country: str = Field(..., description="ISO 3166-1 alpha-2，如 NZ")
    province: str = Field("", description="省/州，如 Auckland")
    subtotal: Decimal = Field(..., ge=0, description="订单商品金额")
    total_weight_kg: Decimal = Field(Decimal("0"), ge=0, description="订单总重量(kg)")
    total_items: int = Field(1, ge=1, description="商品件数")
    length_cm: Optional[Decimal] = None
    width_cm: Optional[Decimal] = None
    height_cm: Optional[Decimal] = None


class ShippingOptionOut(BaseModel):
    method_id: int
    name: str
    carrier_name: Optional[str]
    estimated_days: Optional[str]
    fee: Decimal
    is_free: bool
    pricing_method: str


@router.post("/preview", response_model=List[ShippingOptionOut])
async def preview_shipping(
    body: ShippingPreviewIn,
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_admin_user),
):
    """按收件地址 + 订单信息预览可用运费方案及费用（管理员调试用）"""
    from app.core.services.shipping_calculator import ShippingCalculator
    calculator = ShippingCalculator(db, user.tenant_id)
    options = await calculator.get_available_methods(
        country=body.country,
        province=body.province,
        subtotal=body.subtotal,
        total_weight_kg=body.total_weight_kg,
        total_items=body.total_items,
        length_cm=body.length_cm,
        width_cm=body.width_cm,
        height_cm=body.height_cm,
    )
    return options
