# -*- coding: utf-8 -*-
"""纪念钞解析器 (board 151)

解析纪念钞收购帖文本，提取品种名、价格分类（标十/标百/刀/散张/捆）、数量。
参考 coin_parser 的多模式匹配思路，适配纪念钞价格表达。
"""

from __future__ import annotations

import re

from .base import PriceRecord
from .coin_parser import (
    _looks_like_price,
    _is_noise_name,
    _is_bank_account_row,
    _clean_name,
)


# ============================================================
# 纪念钞品种关键词（按优先级排序，长词优先）
# ============================================================
# 已知品种名（不含号码修饰），长词优先避免短词误匹配（如"小龙钞"被"龙钞"抢先）
BANKNOTE_NAMES = [
    '生肖豹王', '生肖钞王', '迎接新世纪钞', '世纪龙钞',
    '小龙钞', '大龙钞', '龙钞',
    '有钱人', '财神刀', '财神龙', '龙尊',
    '马钞', '蛇钞', '奥运钞', '建国钞', '航天钞',
    '马三', '小司令',
]

# 包装分类关键词
PACKAGE_KEYWORDS = ['标十', '标10', '标百', '刀', '散张', '裸张', '散钞', '捆', '件']

# 号码修饰词前缀
NUMBER_PREFIXES = ['无347', '无47', '无34', '无4', '带4', '无3']

# 论坛模板行（整行匹配则跳过）
TEMPLATE_LINE_PATTERNS = [
    r'^楼主$', r'^评分$', r'^查看$', r'^亮照亮证$', r'^QQ$', r'^确认$',
    r'^营销员第', r'^认证员注', r'^姓名[：:]', r'^地址[：:]',
    r'^电话[：:]', r'^手机[：:]', r'^微信[：:]', r'^QQ[：:]',
    r'^建行[：:]', r'^工行[：:]', r'^中行[：:]', r'^农行[：:]',
    r'^交行[：:]', r'^邮政[：:]', r'^民生[：:]', r'^华夏[：:]',
    r'^管理员注', r'^认证推荐人', r'^电子邮件', r'^备注[：:]',
    r'^此主题相关图片', r'^一尘互评', r'^收货地址', r'^开户行',
]

# 装饰符号（需去除）
DECORATION_PATTERN = re.compile(r'[█★▄▅▆▇███▓▒░◇◆◇◆●○◎※☆♬♪♭♯♮♯▼▽◤◥◄◢━┃┏┓┗┛]')


def preprocess(text: str) -> str:
    """预处理：全角转半角、去装饰符号、去多余空格。"""
    # 全角→半角
    t = text.replace('，', ',').replace('。', '.').replace('；', ';').replace('：', ':')
    t = t.replace('【', '[').replace('】', ']').replace('（', '(').replace('）', ')')
    # 修正连续小数点
    t = re.sub(r'(\d+)\.{2,}(\d+)', r'\1.\2', t)
    # 去装饰符号
    t = DECORATION_PATTERN.sub(' ', t)
    # 统一空格
    t = re.sub(r'[ \t\xa0]+', ' ', t)
    return t.strip()


def _is_template_line(line: str) -> bool:
    """判断是否为论坛模板行（应跳过）。"""
    line = line.strip()
    if not line:
        return True
    for pat in TEMPLATE_LINE_PATTERNS:
        if re.match(pat, line):
            return True
    return False


def _extract_name(text: str) -> str:
    """从文本中提取纪念钞品种名。

    策略：
    1. 剥离动词前缀（收/求/收购/求购/出/出售/高价收购等）
    2. 剥离价格前置（5350收有钱人 → 有钱人）
    3. 匹配已知品种关键词
    4. 附加号码修饰（带4/无4/无47/无347）和包装分类（标十/刀等）
    """
    t = text.strip()

    # 剥离"动词:品种"前缀
    m = re.match(r'^(收购|求购|出售|收|求|出|售)\s*[:：]\s*', t)
    if m:
        t = t[m.end():]

    # 剥离"价格+动词"前缀（如"5350收有钱人"、"5000求购有钱人"）
    m = re.match(r'^(\d+\.?\d*)\s*(元|/)?\s*(高价)?(收购|求购|出售|收|求|出|售)', t)
    if m:
        t = t[m.end():].strip()

    # 剥离"高价/先款/现款+动词"前缀（如"高价收购"、"先款收"、"现款4900收"）
    m = re.match(r'^(高价|先款|现款|高价先款|高价现款)\s*(\d+\.?\d*)?\s*(元)?\s*(收购|求购|出售|收|求|出|售)', t)
    if m:
        t = t[m.end():].strip()
        # 再次剥离可能残留的价格
        m2 = re.match(r'^(\d+\.?\d*)\s*(元)?\s*', t)
        if m2:
            t = t[m2.end():].strip()

    # 剥离"[原创]"等前缀
    t = re.sub(r'^\[原创\]\s*', '', t)
    t = re.sub(r'^\[.*?\]\s*', '', t)

    # 提取号码修饰词
    number_mod = ''
    for prefix in NUMBER_PREFIXES:
        if prefix in t:
            number_mod = prefix
            break

    # 匹配已知品种名
    found_name = ''
    for bn in BANKNOTE_NAMES:
        if bn in t:
            found_name = bn
            break

    if not found_name:
        # 未匹配到已知品种，尝试提取中文开头的文字
        m = re.match(r'^([\u4e00-\u9fff]{2,8}钞)', t)
        if m:
            found_name = m.group(1)
        else:
            return ''

    # 组合：品种名 + 号码修饰（如"龙钞无347"）
    # 如果原文中号码修饰紧挨品种名，保留组合
    result = found_name
    if number_mod and number_mod in t:
        # 检查号码修饰是否在品种名附近
        idx = t.find(found_name)
        after = t[idx + len(found_name):idx + len(found_name) + 6]
        if number_mod in after or number_mod in t[:idx + len(found_name) + 10]:
            result = found_name + ' ' + number_mod

    return _clean_name(result)


def _extract_prices(text: str, name: str) -> dict:
    """提取价格分类。返回 dict: {"标十": "", "标百": "", "刀": "", "散张": "", "捆": ""}."""
    result = {"标十": "", "标百": "", "刀": "", "散张": "", "捆": ""}
    t = text.strip()

    # 模式A：标十/标10 + 价格（正向：标十5500 / 标10：5500）
    for pat in [r'标十\s*[:：]?\s*(\d+\.?\d*)', r'标10\s*[:：]?\s*(\d+\.?\d*)']:
        m = re.search(pat, t)
        if m and _looks_like_price(m.group(1)):
            result["标十"] = m.group(1)
            break

    # 模式B：标百 + 价格
    m = re.search(r'标百\s*[:：]?\s*(\d+\.?\d*)', t)
    if m and _looks_like_price(m.group(1)):
        result["标百"] = m.group(1)

    # 模式C：刀 + 价格（含号码修饰，如"带4刀 2330"、"无47刀 2380"）
    for pat in [r'(?:带4|无4|无47|无347|无34)?\s*刀\s*[:：]?\s*(\d+\.?\d*)',
                r'(\d+\.?\d*)\s*(?:元)?\s*(?:带4|无4|无47|无347|无34)?\s*刀']:
        m = re.search(pat, t)
        if m and _looks_like_price(m.group(1)):
            if float(m.group(1)) >= 100:
                result["刀"] = m.group(1)
                break

    # 模式D：散张/裸张/散钞/单张 + 价格（含"单张:价格(...)"格式）
    for pat in [r'散张\s*[:：]?\s*(\d+\.?\d*)', r'裸张\s*[:：]?\s*(\d+\.?\d*)',
                r'散钞\s*[:：]?\s*(\d+\.?\d*)', r'散\s+(\d+\.?\d*)',
                r'单价\s*[:：]?\s*(\d+\.?\d*)',
                r'单张\s*[:：]?\s*(?:价格)?\s*[\(（]?\s*(\d+\.?\d*)']:
        m = re.search(pat, t)
        if m and _looks_like_price(m.group(1)):
            result["散张"] = m.group(1)
            break

    # 模式D2：价格范围 "价格(90元一120元)" / "价格(90-120)" → 取最低价
    if not result["散张"]:
        m = re.search(r'价格\s*[\(（]\s*(\d+\.?\d*)\s*(?:元)?\s*[一\-~～至到]\s*(\d+\.?\d*)\s*(?:元)?\s*[\)）]', t)
        if m and _looks_like_price(m.group(1)):
            result["散张"] = m.group(1)

    # 模式E：捆 + 价格
    m = re.search(r'捆\s*[:：]?\s*(\d+\.?\d*)', t)
    if m and _looks_like_price(m.group(1)):
        result["捆"] = m.group(1)

    # 模式F：行首裸价（无包装关键词，如"5350收有钱人要五组"）
    # 若行中含包装关键词，则关联到对应包装（如"4900元高价收购有钱人标十龙钞"→标十=4900）
    has_package = any(v for v in result.values())
    if not has_package:
        m = re.match(r'^(\d+\.?\d*)\s*(?:元)?\s*/?\s*(?:组|张|刀)?\s*(?:高价|先款|现款)?\s*(?:收购|求购|收|求|出|售)', t)
        if m and _looks_like_price(m.group(1)):
            price_val = m.group(1)
            fv = float(price_val)
            if ('标十' in t or '标10' in t) and fv >= 50:
                result["标十"] = price_val
            elif '标百' in t and fv >= 100:
                result["标百"] = price_val
            elif '刀' in t and fv >= 100:
                result["刀"] = price_val
            elif '捆' in t:
                result["捆"] = price_val
            else:
                result["散张"] = price_val

    return result


def _extract_quantity(text: str) -> tuple[str, str]:
    """提取需求数量和单位。返回 (quantity, quantity_unit)。"""
    t = text.strip()

    # "要五组" / "要3组" / "要10组"
    m = re.search(r'要\s*(\d+)\s*(组|张|刀|件|捆)', t)
    if m:
        return m.group(1), m.group(2)

    # 中文数字 "要五组" / "要三组"
    cn_map = {'一': '1', '二': '2', '两': '2', '三': '3', '四': '4',
              '五': '5', '六': '6', '七': '7', '八': '8', '九': '9', '十': '10'}
    m = re.search(r'要\s*([一二两三四五六七八九十])\s*(组|张|刀|件|捆)', t)
    if m:
        return cn_map.get(m.group(1), m.group(1)), m.group(2)

    # "1000张" / "5刀" / "3张"
    m = re.search(r'(\d+)\s*(张|刀|件|捆|组)', t)
    if m:
        return m.group(1), m.group(2)

    # "一共要3组"
    m = re.search(r'一共要\s*(\d+)\s*(组|张|刀|件|捆)', t)
    if m:
        return m.group(1), m.group(2)

    # "暂要10组"
    m = re.search(r'暂要\s*(\d+)\s*(组|张|刀|件|捆)', t)
    if m:
        return m.group(1), m.group(2)

    return '', ''


def _is_valid_record(name: str, prices: dict, quantity: str, raw_text: str) -> bool:
    """校验记录是否有效。"""
    if not name or _is_noise_name(name):
        return False
    if _is_bank_account_row(name, raw_text):
        return False
    has_price = any(_looks_like_price(v) for v in prices.values())
    has_qty = bool(quantity.strip())
    return has_price or has_qty


def parse(text: str) -> list[PriceRecord]:
    """纪念钞解析入口。"""
    cleaned = preprocess(text)
    records: list[PriceRecord] = []

    for line in cleaned.split('\n'):
        line = line.strip()
        if not line or _is_template_line(line):
            continue
        # 过滤出售帖（仅保留收购/求购）
        if '出售' in line:
            continue

        # 提取品种名
        name = _extract_name(line)
        if not name:
            continue

        # 提取价格
        prices = _extract_prices(line, name)

        # 提取数量
        quantity, quantity_unit = _extract_quantity(line)

        # 校验
        if not _is_valid_record(name, prices, quantity, line):
            continue

        # 参考价 = 最低有效价
        valid_prices = [float(v) for v in prices.values() if _looks_like_price(v)]
        ref_price = str(min(valid_prices)) if valid_prices else ''

        # 包装分类追加到名称（如"龙钞标十"）
        full_name = name
        for pkg in ['标十', '标百', '刀', '散张', '捆']:
            if prices.get(pkg):
                if pkg not in full_name:
                    full_name = name + ' ' + pkg
                break

        records.append(PriceRecord(
            name=full_name.strip(),
            ref_price=ref_price,
            price_details=prices,
            unit='',
            quantity=quantity,
            quantity_unit=quantity_unit,
            package_req='',
            quality_req='',
            other_notes='',
            raw_text=line,
        ))

    return records
