"""
处罚裁量规则服务 — 管理裁量规则数据库（建表 + CSV 导入 + 查询）

数据来源：单机版/处罚裁量/{地区}/ 下的 CSV 文件
"""
import csv
import os
import re
import logging
from typing import Optional, List, Dict, Any

logger = logging.getLogger(__name__)

# CSV 文件名 → 裁量类型映射（标准格式）
_CSV_TYPE_MAP = {
    "从轻处罚.csv": "从轻处罚",
    "减轻处罚.csv": "减轻处罚",
    "不予处罚.csv": "不予处罚",
    "免于强制措施.csv": "免于强制措施",
}

# 四川省 CSV 文件名 → 解析配置
_SICHUAN_CSV_MAP = {
    "免予行政处罚清单.csv": {"parser": "sichuan_exempt"},
    "市场监管轻微行政违法行为不予处罚清单.csv": {"parser": "sichuan_simple", "rule_type": "轻微免罚", "domain": "食品安全"},
    "市场监管行政违法行为首违不罚清单.csv": {"parser": "sichuan_simple", "rule_type": "首违免罚", "domain": "食品安全"},
}

# 所有已知 CSV 文件名集合
_ALL_CSV_PATTERNS = set(_CSV_TYPE_MAP.keys()) | set(_SICHUAN_CSV_MAP.keys())

# 裁量数据根目录（多路径兼容：开发环境、打包 EXE、直接放在根目录）
_this_dir = os.path.dirname(os.path.abspath(__file__))
_data_candidates = [
    os.path.normpath(os.path.join(_this_dir, "..", "..", "..", "单机版", "处罚裁量")),  # 开发：项目根/单机版/处罚裁量
    os.path.normpath(os.path.join(_this_dir, "..", "..", "..", "处罚裁量")),           # 打包：EXE根/处罚裁量
    os.path.normpath(os.path.join(_this_dir, "..", "..", "处罚裁量")),                 # 打包备选
]
_DATA_ROOT = next((p for p in _data_candidates if os.path.isdir(p)), _data_candidates[0])

# 兼容旧路径（zhishiku 目录已不再打包，此路径仅用于历史数据兼容）
_LEGACY_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", "data", "zhishiku", "standard", "新疆裁量-已处理"))
_LEGACY_FILES = {
    "从轻处罚": "新疆维吾尔自治区 新疆生产建设兵团市场监管领域从轻行政处罚事项清单（2025 年版）.csv",
    "减轻处罚": "市场监管领域减轻行政处罚事项清单（2025年版）.csv",
    "不予处罚": "新疆维吾尔自治区 新疆生产建设兵团市场监管领域轻微违法行为不予行政处罚清单（2025 版）.csv",
    "免于强制措施": "新疆维吾尔自治区 新疆生产建设兵团市场监管领域免于行政强制措施清单（2025 年版）.csv",
}

# 管理领域名称统一映射
_DOMAIN_MAPPING = {
    "食品安全监管": "食品安全监管", "食品监管": "食品安全监管", "食品安全": "食品安全监管",
    "知识产权管理": "知识产权监管", "知识产权监管": "知识产权监管", "知识产权": "知识产权监管",
    "专利监管": "知识产权监管", "商标监管": "知识产权监管",
    "登记注册监管": "登记注册监管", "登记监管": "登记注册监管", "准营监管": "登记注册监管",
    "注册登记": "登记注册监管",
    "特种设备监管": "特种设备安全监管",
    "计量监管": "计量监管", "计量管理": "计量监管",
    "不正当竞争执法": "公平竞争监管", "竞争执法": "公平竞争监管", "公平竞争": "公平竞争监管",
    "标准监管": "标准化监管",
    "广告": "广告监管",
    "价格": "价格监管",
    "市场秩序": "市场秩序监管",
    "产品质量": "产品质量监管",
    "认证认可": "认证认可监管",
}

# 条件文本起始模式（用于分割"设定依据"和"适用情形"）
_CONDITION_START = re.compile(
    r'^(1\.|有下列|同时符合|以下条件|满足以下|主动消除|受他人胁迫|主动供述|配合行政|'
    r'已满十四|初次违法|违法行为轻微|当事人因残疾|尚未完全|同时满足|以下情形)'
)


def _normalize_domain(raw: str) -> str:
    raw = raw.strip()
    return _DOMAIN_MAPPING.get(raw, raw)


def _parse_row_smart(row: List[str], rule_type: str, region: str, row_num: int) -> Optional[Dict[str, Any]]:
    """
    智能解析一行 CSV 数据，处理因法律文本中未加引号的逗号导致的字段错位。

    策略：
    1. [0]=序号, [1]=领域, [2]=违法事项 固定
    2. 从后往前找"法律依据"字段（含"《行政处罚法》"或"《行政强制法》"）
    3. 法律依据之后的字段 = 裁量幅度 + 权力层级
    4. 法律依据之前的字段 = 设定依据 + 适用情形（用正则分割）
    """
    n = len(row)
    if n < 5:
        return None

    # 基础字段
    domain = _normalize_domain(row[1]) if n > 1 else ""
    violation_item = row[2].strip() if n > 2 else ""
    if not violation_item:
        return None

    # 从后往前找法律依据字段
    legal_basis_idx = -1
    for i in range(n - 1, 2, -1):
        if '行政处罚法' in row[i] or '行政强制法' in row[i] or '行政处罚法' in row[i]:
            if '《' in row[i]:
                legal_basis_idx = i
                break

    if legal_basis_idx < 0:
        # 找不到法律依据，取倒数第3个字段
        legal_basis_idx = max(3, n - 3)

    # 提取法律依据之后的字段
    legal_basis = row[legal_basis_idx].strip()
    penalty_result = row[legal_basis_idx + 1].strip() if legal_basis_idx + 1 < n - 1 else ""
    power_level = row[n - 1].strip()

    # 中间字段 = 设定依据 + 适用情形
    middle_fields = [v.strip() for v in row[3:legal_basis_idx] if v.strip()]
    middle_text = "，".join(middle_fields)

    # 用正则分割：找到条件文本的起始位置
    conditions_raw = ""
    law_text = middle_text
    for i, field in enumerate(middle_fields):
        if _CONDITION_START.match(field):
            law_text = "，".join(middle_fields[:i])
            conditions_raw = "，".join(middle_fields[i:])
            break

    # 裁量类型对应的条件依据字段
    if rule_type in ("从轻处罚", "减轻处罚"):
        conditions_legal_basis = legal_basis
    elif rule_type == "免于强制措施":
        conditions_legal_basis = legal_basis
    else:
        conditions_legal_basis = ""

    return {
        "rule_type": rule_type,
        "domain": domain,
        "violation_item": violation_item,
        "legal_basis_violation": law_text,
        "conditions_raw": conditions_raw,
        "conditions_legal_basis": conditions_legal_basis,
        "penalty_result": penalty_result,
        "power_level": power_level,
        "remark": "",
        "source_file": region,
        "source_row": row_num,
    }


def _parse_csv_file(file_path: str, rule_type: str, region: str) -> List[Dict[str, Any]]:
    """解析单个 CSV 文件（标准格式）"""
    rules = []
    if not os.path.exists(file_path):
        logger.warning(f"CSV 文件不存在: {file_path}")
        return rules

    with open(file_path, "r", encoding="utf-8-sig") as f:
        reader = csv.reader(f)
        header = next(reader)  # skip header
        for idx, row in enumerate(reader, start=2):
            rule = _parse_row_smart(row, rule_type, region, idx)
            if rule:
                rules.append(rule)

    return rules


def _parse_sichuan_csv_file(file_path: str, config: Dict[str, Any], region: str) -> List[Dict[str, Any]]:
    """解析四川省格式 CSV 文件（使用 DictReader 按列名映射）"""
    rules = []
    if not os.path.exists(file_path):
        logger.warning(f"CSV 文件不存在: {file_path}")
        return rules

    parser = config.get("parser", "")

    with open(file_path, "r", encoding="utf-8-sig") as f:
        reader = csv.DictReader(f)
        for idx, row in enumerate(reader, start=2):
            if parser == "sichuan_exempt":
                rule = _parse_sichuan_exempt_row(row, region, idx)
            elif parser == "sichuan_simple":
                rule = _parse_sichuan_simple_row(row, config, region, idx)
            else:
                continue
            if rule:
                rules.append(rule)

    return rules


def _parse_sichuan_exempt_row(row: Dict[str, str], region: str, row_num: int) -> Optional[Dict[str, Any]]:
    """解析四川省免予行政处罚清单的一行数据
    列：领域, 违法行为名称, 法律依据, 适用条件, 免罚类型
    """
    violation_item = row.get("违法行为名称", "").strip()
    if not violation_item:
        return None

    rule_type = row.get("免罚类型", "").strip()
    if not rule_type:
        return None

    domain = _normalize_domain(row.get("领域", "").strip())
    legal_basis = row.get("法律依据", "").strip()
    conditions = row.get("适用条件", "").strip()

    return {
        "rule_type": rule_type,
        "domain": domain,
        "violation_item": violation_item,
        "legal_basis_violation": legal_basis,
        "conditions_raw": conditions,
        "conditions_legal_basis": "",
        "penalty_result": "",
        "power_level": "",
        "remark": "",
        "source_file": region,
        "source_row": row_num,
    }


def _parse_sichuan_simple_row(row: Dict[str, str], config: Dict[str, Any], region: str, row_num: int) -> Optional[Dict[str, Any]]:
    """解析四川省简单格式 CSV 的一行数据（轻微免罚/首违不罚）
    列：违法行为类型, 处罚依据, 免罚条件
    """
    violation_item = row.get("违法行为类型", "").strip()
    if not violation_item:
        return None

    rule_type = config.get("rule_type", "")
    domain = _normalize_domain(config.get("domain", ""))
    legal_basis = row.get("处罚依据", "").strip()
    conditions = row.get("免罚条件", "").strip()

    return {
        "rule_type": rule_type,
        "domain": domain,
        "violation_item": violation_item,
        "legal_basis_violation": legal_basis,
        "conditions_raw": conditions,
        "conditions_legal_basis": "",
        "penalty_result": "",
        "power_level": "",
        "remark": "",
        "source_file": region,
        "source_row": row_num,
    }


def _discover_regions() -> Dict[str, str]:
    """扫描 单机版/处罚裁量/ 目录，发现可用的地区数据"""
    regions = {}
    if os.path.isdir(_DATA_ROOT):
        for name in sorted(os.listdir(_DATA_ROOT)):
            region_dir = os.path.join(_DATA_ROOT, name)
            if os.path.isdir(region_dir):
                has_csv = any(f in _ALL_CSV_PATTERNS for f in os.listdir(region_dir) if f.endswith(".csv"))
                if has_csv:
                    regions[name] = region_dir

    # 兼容旧路径
    if os.path.isdir(_LEGACY_ROOT) and "新疆维吾尔自治区" not in regions:
        has_csv = any(os.path.exists(os.path.join(_LEGACY_ROOT, v)) for v in _LEGACY_FILES.values())
        if has_csv:
            regions["新疆维吾尔自治区（旧）"] = _LEGACY_ROOT

    return regions


async def ensure_table_and_data(conn, cur):
    """建表 + 导入数据（幂等）"""

    await cur.execute("""
        CREATE TABLE IF NOT EXISTS discretion_rules (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            rule_type TEXT NOT NULL,
            domain TEXT NOT NULL,
            violation_item TEXT NOT NULL,
            legal_basis_violation TEXT,
            conditions_raw TEXT,
            conditions_legal_basis TEXT,
            penalty_result TEXT,
            power_level TEXT,
            remark TEXT,
            source_file TEXT,
            source_row INTEGER,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    await cur.execute("""
        CREATE TABLE IF NOT EXISTS discretion_domains (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            domain_name TEXT NOT NULL UNIQUE,
            sort_num INTEGER DEFAULT 0
        )
    """)
    await cur.execute("CREATE INDEX IF NOT EXISTS idx_dr_rule_type ON discretion_rules(rule_type)")
    await cur.execute("CREATE INDEX IF NOT EXISTS idx_dr_domain ON discretion_rules(domain)")
    await cur.execute("CREATE INDEX IF NOT EXISTS idx_dr_violation ON discretion_rules(violation_item)")
    await cur.execute("CREATE INDEX IF NOT EXISTS idx_dr_source ON discretion_rules(source_file)")

    # 检查已有数据
    await cur.execute("SELECT COUNT(*) as cnt FROM discretion_rules")
    row = await cur.fetchone()
    cnt = row["cnt"] if row else 0

    # 获取已导入的区域
    existing_regions = set()
    if cnt > 0:
        await cur.execute("SELECT DISTINCT source_file FROM discretion_rules")
        existing_regions = {r["source_file"] for r in await cur.fetchall()}

        # 检查旧格式
        sample = list(existing_regions)[0] if existing_regions else ""
        if sample and sample.endswith(".csv"):
            logger.info("检测到旧格式裁量数据，清空并重新导入...")
            await cur.execute("DELETE FROM discretion_rules")
            await cur.execute("DELETE FROM discretion_domains")
            await conn.commit()
            existing_regions = set()

    # 发现并导入
    regions = _discover_regions()
    new_regions = {k: v for k, v in regions.items() if k not in existing_regions}

    if not new_regions:
        logger.info(f"discretion_rules 表已有 {cnt} 条数据，所有地区已导入，跳过")
        return

    total_rules = 0
    all_domains = set()

    for region_name, region_dir in new_regions.items():
        logger.info(f"导入裁量数据: {region_name}")
        region_rules = []

        if "（旧）" in region_name:
            for rule_type, filename in _LEGACY_FILES.items():
                fpath = os.path.join(region_dir, filename)
                rules = _parse_csv_file(fpath, rule_type, region_name)
                region_rules.extend(rules)
                logger.info(f"  [{rule_type}] {len(rules)} 条")
        else:
            for fname in sorted(os.listdir(region_dir)):
                fpath = os.path.join(region_dir, fname)
                if fname in _CSV_TYPE_MAP:
                    rules = _parse_csv_file(fpath, _CSV_TYPE_MAP[fname], region_name)
                    region_rules.extend(rules)
                    logger.info(f"  [{_CSV_TYPE_MAP[fname]}] {len(rules)} 条")
                elif fname in _SICHUAN_CSV_MAP:
                    config = _SICHUAN_CSV_MAP[fname]
                    rules = _parse_sichuan_csv_file(fpath, config, region_name)
                    rule_type_label = config.get("rule_type", config.get("parser", ""))
                    region_rules.extend(rules)
                    logger.info(f"  [{rule_type_label}] {len(rules)} 条")

        for rule in region_rules:
            await cur.execute("""
                INSERT INTO discretion_rules
                    (rule_type, domain, violation_item, legal_basis_violation,
                     conditions_raw, conditions_legal_basis, penalty_result,
                     power_level, remark, source_file, source_row)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            """, (
                rule["rule_type"], rule["domain"], rule["violation_item"],
                rule["legal_basis_violation"], rule["conditions_raw"],
                rule["conditions_legal_basis"], rule["penalty_result"],
                rule["power_level"], rule["remark"], rule["source_file"],
                rule["source_row"],
            ))

        all_domains.update(r["domain"] for r in region_rules if r["domain"])
        total_rules += len(region_rules)

    for i, domain in enumerate(sorted(all_domains)):
        await cur.execute("INSERT OR IGNORE INTO discretion_domains (domain_name, sort_num) VALUES (%s, %s)", (domain, i))

    await conn.commit()
    logger.info(f"成功导入 {total_rules} 条裁量规则（{len(regions)} 个地区），{len(all_domains)} 个管理领域")


async def get_source_files(conn, cur) -> List[Dict[str, str]]:
    await cur.execute("SELECT source_file, COUNT(*) as rule_count FROM discretion_rules GROUP BY source_file ORDER BY source_file")
    rows = await cur.fetchall()
    return [{"source_file": r["source_file"] or "", "label": r["source_file"] or "未知", "rule_count": r["rule_count"]} for r in rows]


async def search_rules(conn, cur, rule_type=None, domain=None, keyword=None, source_file=None, page=1, page_size=20):
    conditions, params = [], []
    if rule_type:
        conditions.append("rule_type = %s"); params.append(rule_type)
    if domain:
        conditions.append("domain = %s"); params.append(domain)
    if keyword:
        conditions.append("violation_item LIKE %s"); params.append(f"%{keyword}%")
    if source_file:
        conditions.append("source_file = %s"); params.append(source_file)

    where = " AND ".join(conditions) if conditions else "1=1"
    await cur.execute(f"SELECT COUNT(*) as cnt FROM discretion_rules WHERE {where}", params)
    total = (await cur.fetchone())["cnt"]

    offset = (page - 1) * page_size
    await cur.execute(f"SELECT * FROM discretion_rules WHERE {where} ORDER BY id LIMIT %s OFFSET %s", params + [page_size, offset])
    rows = await cur.fetchall()
    return {"list": [dict(r) for r in rows], "total": total, "page": page, "page_size": page_size}


async def get_domains(conn, cur, source_file=None):
    if source_file:
        await cur.execute("SELECT DISTINCT domain FROM discretion_rules WHERE source_file = %s ORDER BY domain", (source_file,))
    else:
        await cur.execute("SELECT domain_name FROM discretion_domains ORDER BY sort_num")
    return [r[list(r.keys())[0]] for r in await cur.fetchall()]


async def get_rule_by_id(conn, cur, rule_id):
    await cur.execute("SELECT * FROM discretion_rules WHERE id = %s", (rule_id,))
    row = await cur.fetchone()
    return dict(row) if row else None


async def clear_and_reimport(conn, cur):
    await cur.execute("DELETE FROM discretion_rules")
    await cur.execute("DELETE FROM discretion_domains")
    await conn.commit()
    logger.info("已清空裁量规则数据，开始重新导入...")
    await ensure_table_and_data(conn, cur)
