# -*- coding: utf-8 -*-
"""
管理端路由 — 注册体系（连接器/租户/店铺/简写ID/店铺同步）。

端点清单：
- GET  /api/admin/tenants                       — 所有活跃租户列表
- GET  /api/admin/tenants/{tenant_id}/sites     — 指定租户下所有活跃店铺
- PUT  /api/admin/sites/{site_id}/site-code     — 设置/修改简写ID
- GET  /api/admin/sites/{site_id}/site-code-history — 简写ID 变更历史
- POST /api/admin/sites/sync                    — 手动触发店铺同步
- POST /api/admin/sites/sync/internal           — 内部 API：ETL DWD 完成后触发同步（无认证，隐藏）

除 /sites/sync/internal 外，所有端点要求 JWT + site_admin 或 tenant_admin 角色。

需求: A2.1, A2.2, A2.4, A2.5, A3.1, A3.2, A3.3, A3.4, A5.1, A5.2, A5.3, A5.4
"""

from __future__ import annotations

import logging
import re

import psycopg2
from fastapi import APIRouter, Depends, HTTPException, status
from psycopg2.extensions import connection as PgConnection

from app.auth.dependencies import CurrentUser, get_current_user
from app.config import (
    ETL_DB_HOST,
    ETL_DB_NAME,
    ETL_DB_PASSWORD,
    ETL_DB_PORT,
    ETL_DB_USER,
)
from app.database import get_connection
from app.schemas.admin_registry import (
    CreateSiteRequest,
    SiteCodeHistoryItem,
    SiteCodeResult,
    SiteItem,
    SiteSyncResult,
    TenantItem,
    UpdateSiteCodeRequest,
)

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/admin", tags=["admin-registry"])

# 简写ID 格式：前 3 位字母/数字 + 后 3 位数字（共 6 位）
_SITE_CODE_PATTERN = re.compile(r"^[A-Z0-9]{3}\d{3}$")


# ── ETL 库直连（无 RLS，管理端同步专用） ─────────────────


def _get_etl_admin_connection() -> PgConnection:
    """获取 ETL 库只读连接（无 RLS 隔离），用于管理端跨站点同步。

    与 database.get_etl_readonly_connection 不同：
    - 不设置 app.current_site_id（需要读取所有站点数据）
    - 仍设置 read_only 防止误写
    """
    conn = psycopg2.connect(
        host=ETL_DB_HOST,
        port=ETL_DB_PORT,
        user=ETL_DB_USER,
        password=ETL_DB_PASSWORD,
        dbname=ETL_DB_NAME,
    )
    try:
        conn.autocommit = False
        with conn.cursor() as cur:
            cur.execute("SET default_transaction_read_only = on")
        conn.commit()
    except Exception:
        conn.close()
        raise
    return conn


# ── 店铺同步核心逻辑 ─────────────────────────────────────


def sync_sites_from_etl() -> SiteSyncResult:
    """从 ETL 库 dwd.dim_site 同步店铺到 biz.sites。

    逻辑：
    1. 读取 dwd.dim_site（scd2_is_current=1）获取当前有效店铺
    2. 对比 biz.sites：
       - 新 site_id → INSERT（site_code 留空，tenant_id 通过 dim_site.tenant_id 映射 biz.tenants）
       - 已有 site_id 且 shop_name/site_label 变更 → UPDATE
    3. 不删除已有记录

    需求: A5.1, A5.2
    """
    # 1. 从 ETL 库读取当前有效店铺
    etl_conn = _get_etl_admin_connection()
    try:
        with etl_conn.cursor() as cur:
            cur.execute(
                """
                SELECT site_id, tenant_id, shop_name, site_label
                FROM dwd.dim_site
                WHERE scd2_is_current = 1
                """
            )
            etl_sites = cur.fetchall()
    finally:
        etl_conn.close()

    if not etl_sites:
        return SiteSyncResult(inserted=0, updated=0)

    # 2. 在 app 库中执行对比和写入
    app_conn = get_connection()
    inserted = 0
    updated = 0
    try:
        with app_conn.cursor() as cur:
            # 构建 tenant_id → biz.tenants.id 映射
            cur.execute("SELECT tenant_id, id FROM biz.tenants WHERE is_active = true")
            tenant_map: dict[int, int] = {row[0]: row[1] for row in cur.fetchall()}

            # 获取 biz.sites 现有数据（site_id → (biz_id, site_name, site_label)）
            cur.execute(
                "SELECT site_id, id, site_name, site_label FROM biz.sites"
            )
            existing: dict[int, tuple[int, str | None, str | None]] = {
                row[0]: (row[1], row[2], row[3]) for row in cur.fetchall()
            }

            for etl_site_id, etl_tenant_id, etl_shop_name, etl_site_label in etl_sites:
                biz_tenant_id = tenant_map.get(etl_tenant_id)
                if biz_tenant_id is None:
                    # 租户未注册，跳过
                    logger.warning(
                        "同步跳过: site_id=%s 的 tenant_id=%s 在 biz.tenants 中不存在",
                        etl_site_id, etl_tenant_id,
                    )
                    continue

                if etl_site_id not in existing:
                    # 新增店铺：site_code 留空
                    cur.execute(
                        """
                        INSERT INTO biz.sites (tenant_id, site_id, site_name, site_label)
                        VALUES (%s, %s, %s, %s)
                        """,
                        (biz_tenant_id, etl_site_id, etl_shop_name, etl_site_label),
                    )
                    inserted += 1
                else:
                    # 已有店铺：检查名称/标签是否变更
                    _, cur_name, cur_label = existing[etl_site_id]
                    if cur_name != etl_shop_name or cur_label != etl_site_label:
                        cur.execute(
                            """
                            UPDATE biz.sites
                            SET site_name = %s, site_label = %s, updated_at = NOW()
                            WHERE site_id = %s
                            """,
                            (etl_shop_name, etl_site_label, etl_site_id),
                        )
                        updated += 1

        app_conn.commit()
    except Exception:
        app_conn.rollback()
        raise
    finally:
        app_conn.close()

    logger.info("店铺同步完成: 新增 %d, 更新 %d", inserted, updated)
    return SiteSyncResult(inserted=inserted, updated=updated)


# ── 管理端权限依赖 ──────────────────────────────────────


def _require_admin():
    """管理端依赖：要求 JWT 中角色包含 site_admin 或 tenant_admin。"""

    async def _dependency(
        user: CurrentUser = Depends(get_current_user),
    ) -> CurrentUser:
        admin_roles = {"site_admin", "tenant_admin"}
        if not admin_roles.intersection(user.roles):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="需要管理员权限（site_admin 或 tenant_admin）",
            )
        return user

    return _dependency


# ── GET /api/admin/tenants ────────────────────────────────


@router.get("/tenants")
async def list_tenants(
    user: CurrentUser = Depends(_require_admin()),
) -> list[TenantItem]:
    """
    所有活跃租户列表（含连接器名称）。

    JOIN biz.connectors 获取 connector_name。
    需求 A2.1
    """
    conn = get_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT t.id, t.tenant_id, t.tenant_name,
                       c.display_name AS connector_name, t.is_active
                FROM biz.tenants t
                JOIN biz.connectors c ON c.id = t.connector_id
                WHERE t.is_active = true
                ORDER BY t.id
                """
            )
            rows = cur.fetchall()
    finally:
        conn.close()

    return [
        TenantItem(
            id=r[0],
            tenant_id=r[1],
            tenant_name=r[2],
            connector_name=r[3],
            is_active=r[4],
        )
        for r in rows
    ]


# ── GET /api/admin/tenants/{tenant_id}/sites ──────────────


@router.get("/tenants/{tenant_id}/sites")
async def list_tenant_sites(
    tenant_id: int,
    user: CurrentUser = Depends(_require_admin()),
) -> list[SiteItem]:
    """
    指定租户下所有活跃店铺。

    tenant_id 参数支持两种格式：
    - 上游系统租户 ID（BIGINT，如 2790683160709957）
    - 内部主键（SERIAL，如 1, 2, 3...）
    自动判断：> 10000 视为上游 ID，否则视为内部 PK。

    需求 A2.2
    """
    conn = get_connection()
    try:
        with conn.cursor() as cur:
            # CHANGE 2026-03-22 | Prompt: 管辖门店下拉为空 | 兼容上游 tenant_id 和内部 PK
            # 自动判断：上游 ID 是 BIGINT（远大于内部 SERIAL），阈值 10000 足够区分
            if tenant_id > 10000:
                cur.execute(
                    "SELECT id FROM biz.tenants WHERE tenant_id = %s AND is_active = true",
                    (tenant_id,),
                )
            else:
                cur.execute(
                    "SELECT id FROM biz.tenants WHERE id = %s AND is_active = true",
                    (tenant_id,),
                )
            row = cur.fetchone()
            if row is None:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail="租户不存在",
                )
            internal_tenant_id = row[0]

            cur.execute(
                """
                SELECT id, site_id, site_name, site_code, site_label, is_active
                FROM biz.sites
                WHERE tenant_id = %s AND is_active = true
                ORDER BY site_id
                """,
                (internal_tenant_id,),
            )
            rows = cur.fetchall()
    finally:
        conn.close()

    return [
        SiteItem(
            id=r[0],
            site_id=r[1],
            site_name=r[2],
            site_code=r[3],
            site_label=r[4],
            is_active=r[5],
        )
        for r in rows
    ]


# ── PUT /api/admin/sites/{site_id}/site-code ──────────────


@router.put("/sites/{site_id}/site-code")
async def update_site_code(
    site_id: int,
    body: UpdateSiteCodeRequest,
    user: CurrentUser = Depends(_require_admin()),
) -> SiteCodeResult:
    """
    设置/修改店铺简写ID，事务内执行历史记录管理。

    校验规则：
    - 格式：6 位，前 3 位字母/数字 + 后 3 位数字，统一大写
    - 全局唯一：biz.sites.site_code + biz.site_code_history.site_code

    事务步骤：
    a. 旧 code 在 site_code_history 中标记 is_current=false, retired_at=NOW()
    b. 新 code 插入 site_code_history（is_current=true）
    c. 更新 biz.sites.site_code
    d. 检查旧 code 是否有未审核申请引用，无引用则从 history 中删除旧记录

    需求 A2.4, A3.1, A3.2, A3.3, A3.4
    """
    new_code = body.new_code.strip().upper()

    # ── 格式校验 ──
    if not _SITE_CODE_PATTERN.match(new_code):
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="简写ID 格式错误，需 6 位（3+3 模式：前 3 位字母/数字 + 后 3 位数字）",
        )

    conn = get_connection()
    try:
        with conn.cursor() as cur:
            # ── 校验店铺存在 ──
            cur.execute(
                "SELECT site_id, site_code FROM biz.sites WHERE id = %s",
                (site_id,),
            )
            site_row = cur.fetchone()
            if site_row is None:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail="店铺不存在",
                )
            db_site_id = site_row[0]  # biz.sites.site_id（上游 ID）
            old_code = site_row[1]

            # ── 全局唯一性校验（biz.sites + biz.site_code_history） ──
            cur.execute(
                """
                SELECT 1 FROM biz.sites
                WHERE site_code = %s AND id != %s
                LIMIT 1
                """,
                (new_code, site_id),
            )
            if cur.fetchone():
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail=f"简写ID '{new_code}' 已被使用",
                )

            cur.execute(
                """
                SELECT 1 FROM biz.site_code_history
                WHERE site_code = %s AND site_id != %s
                LIMIT 1
                """,
                (new_code, db_site_id),
            )
            if cur.fetchone():
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail=f"简写ID '{new_code}' 已被使用",
                )

            # ── 事务内执行变更 ──

            # a. 旧 code 标记 retired
            history_cleaned = False
            if old_code:
                cur.execute(
                    """
                    UPDATE biz.site_code_history
                    SET is_current = false, retired_at = NOW()
                    WHERE site_id = %s AND site_code = %s AND is_current = true
                    """,
                    (db_site_id, old_code),
                )

            # b. 新 code 插入 history
            cur.execute(
                """
                INSERT INTO biz.site_code_history (site_id, site_code, is_current)
                VALUES (%s, %s, true)
                """,
                (db_site_id, new_code),
            )

            # c. 更新 biz.sites.site_code
            cur.execute(
                """
                UPDATE biz.sites SET site_code = %s, updated_at = NOW()
                WHERE id = %s
                """,
                (new_code, site_id),
            )

            # d. 检查旧 code 是否有未审核申请引用，无引用则清理历史
            if old_code:
                cur.execute(
                    """
                    SELECT 1 FROM auth.user_applications
                    WHERE site_code = %s AND status = 'pending'
                    LIMIT 1
                    """,
                    (old_code,),
                )
                has_pending = cur.fetchone() is not None

                if not has_pending:
                    cur.execute(
                        """
                        DELETE FROM biz.site_code_history
                        WHERE site_id = %s AND site_code = %s AND is_current = false
                        """,
                        (db_site_id, old_code),
                    )
                    history_cleaned = True

        conn.commit()
    except HTTPException:
        conn.rollback()
        raise
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()

    return SiteCodeResult(
        site_id=db_site_id,
        old_code=old_code,
        new_code=new_code,
        history_cleaned=history_cleaned,
    )


# ── GET /api/admin/sites/{site_id}/site-code-history ──────


@router.get("/sites/{site_id}/site-code-history")
async def get_site_code_history(
    site_id: int,
    user: CurrentUser = Depends(_require_admin()),
) -> list[SiteCodeHistoryItem]:
    """
    查看简写ID 变更历史。

    需求 A2.5
    """
    conn = get_connection()
    try:
        with conn.cursor() as cur:
            # 校验店铺存在，获取上游 site_id
            cur.execute(
                "SELECT site_id FROM biz.sites WHERE id = %s",
                (site_id,),
            )
            site_row = cur.fetchone()
            if site_row is None:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail="店铺不存在",
                )
            db_site_id = site_row[0]

            cur.execute(
                """
                SELECT id, site_code, is_current, created_at, retired_at
                FROM biz.site_code_history
                WHERE site_id = %s
                ORDER BY created_at DESC
                """,
                (db_site_id,),
            )
            rows = cur.fetchall()
    finally:
        conn.close()

    return [
        SiteCodeHistoryItem(
            id=r[0],
            site_code=r[1],
            is_current=r[2],
            created_at=r[3],
            retired_at=r[4],
        )
        for r in rows
    ]


# ── POST /api/admin/sites/sync ────────────────────────────


@router.post("/sites/sync")
async def sync_sites(
    user: CurrentUser = Depends(_require_admin()),
) -> SiteSyncResult:
    """
    手动触发店铺同步：从 ETL 库 dwd.dim_site 同步到 biz.sites。

    返回同步结果（新增数/更新数）。

    需求 A5.3
    """
    return sync_sites_from_etl()

# ── POST /api/admin/sites/sync/internal ───────────────────


@router.post("/sites/sync/internal", include_in_schema=False)
async def sync_sites_internal() -> SiteSyncResult:
    """内部 API：ETL DWD 完成后触发店铺同步。

    不需要 JWT 认证（内部调用），通过 include_in_schema=False 隐藏。
    后续可添加 API key 或 IP 白名单认证。

    需求 A5.4
    """
    return sync_sites_from_etl()


# ── POST /api/admin/sites（测试功能：手动创建店铺） ────────


@router.post("/sites", status_code=status.HTTP_201_CREATED)
async def create_site(
    body: CreateSiteRequest,
    user: CurrentUser = Depends(_require_admin()),
):
    """
    手动创建店铺（测试功能）。

    向 biz.sites 插入一条记录，可选指定 site_code。
    site_id 和 site_code 需全局唯一，冲突返回 409。
    """
    conn = get_connection()
    try:
        with conn.cursor() as cur:
            # 校验 tenant_id 存在
            cur.execute(
                "SELECT id FROM biz.tenants WHERE tenant_id = %s AND is_active = true",
                (body.tenant_id,),
            )
            tenant_row = cur.fetchone()
            if tenant_row is None:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail="租户不存在",
                )
            internal_tenant_id = tenant_row[0]

            # site_code 格式校验（如果提供）
            site_code = None
            if body.site_code:
                site_code = body.site_code.strip().upper()
                if not _SITE_CODE_PATTERN.match(site_code):
                    raise HTTPException(
                        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                        detail="简写ID 格式错误，需 6 位（3+3 格式）",
                    )

            cur.execute(
                """
                INSERT INTO biz.sites (tenant_id, site_id, site_name, site_code)
                VALUES (%s, %s, %s, %s)
                RETURNING id, site_id, site_name, site_code
                """,
                (internal_tenant_id, body.site_id, body.site_name, site_code),
            )
            row = cur.fetchone()

            # 如果有 site_code，同步插入 history
            if site_code:
                cur.execute(
                    """
                    INSERT INTO biz.site_code_history (site_id, site_code, is_current)
                    VALUES (%s, %s, true)
                    """,
                    (body.site_id, site_code),
                )

        conn.commit()
    except HTTPException:
        conn.rollback()
        raise
    except psycopg2.errors.UniqueViolation as e:
        conn.rollback()
        detail = str(e)
        if "site_id" in detail:
            raise HTTPException(status_code=409, detail="site_id 已存在")
        if "site_code" in detail:
            raise HTTPException(status_code=409, detail="简写ID 已被占用")
        raise HTTPException(status_code=409, detail="唯一约束冲突")
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()

    return {"id": row[0], "siteId": row[1], "siteName": row[2], "siteCode": row[3]}


# ── DELETE /api/admin/sites/{site_id}（测试功能：删除店铺） ─


@router.delete("/sites/{site_id}")
async def delete_site(
    site_id: int,
    user: CurrentUser = Depends(_require_admin()),
):
    """
    删除店铺（测试功能，硬删除）。

    同时清理 site_code_history 中的关联记录。
    site_id 参数为 biz.sites.id（内部主键）。
    """
    conn = get_connection()
    try:
        with conn.cursor() as cur:
            # 获取上游 site_id 用于清理 history
            cur.execute(
                "SELECT site_id FROM biz.sites WHERE id = %s",
                (site_id,),
            )
            row = cur.fetchone()
            if row is None:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail="店铺不存在",
                )
            upstream_site_id = row[0]

            # 清理 site_code_history
            cur.execute(
                "DELETE FROM biz.site_code_history WHERE site_id = %s",
                (upstream_site_id,),
            )

            # 删除店铺
            cur.execute("DELETE FROM biz.sites WHERE id = %s", (site_id,))

        conn.commit()
    except HTTPException:
        conn.rollback()
        raise
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()

    return {"id": site_id}
