from __future__ import annotations

import json
import re
from typing import Any


def profiles(rows: tuple[dict[str, Any], ...]) -> list[dict[str, Any]]:
    result = []
    seen = set()
    for row in rows:
        name = _text(row.get("name"))
        if not name or name in seen:
            continue
        seen.add(name)
        organizations = _organizations(row.get("orgs"))
        result.append(
            {
                "name": name,
                "description": _text(row.get("desc")),
                "organizations": organizations,
                "organization_count": len(organizations),
            }
        )
    return result


def build_dragon_list(
    *,
    trade_date: str,
    official_rows: tuple[dict[str, Any], ...] | None,
    profile_rows: tuple[dict[str, Any], ...] | None,
    stock_rows: tuple[dict[str, Any], ...] | None,
    seat_rows: tuple[dict[str, Any], ...] | None,
    aliases: dict[str, str],
) -> dict[str, Any]:
    profile_items = profiles(profile_rows or ())
    profile_map = {str(item["name"]): item for item in profile_items}
    organization_map = {
        organization: str(item["name"])
        for item in profile_items
        for organization in item["organizations"]
    }
    stocks = _stock_context(stock_rows or ())
    operations = _official_operations(official_rows or (), profile_map, stocks)
    official_keys = {
        (str(item["identifier"]), str(item["seat_name"]), round(float(item["net_million"]), 2))
        for item in operations
    }
    for row in seat_rows or ():
        operation = _seat_operation(row, stocks, aliases, organization_map)
        key = (
            str(operation["identifier"]),
            str(operation["seat_name"]),
            round(float(operation["net_million"]), 2),
        )
        if key not in official_keys:
            operations.append(operation)

    traders = _aggregate_traders(operations, profile_map)
    unclassified = _aggregate_unclassified(operations)
    official_stock_count = len(stocks)
    detail_available = official_rows is not None or seat_rows is not None
    detail_count = len(official_rows or ()) + len(seat_rows or ())
    recognized_count = sum(bool(item["recognized"]) for item in operations)
    if official_rows is None and stock_rows is None and seat_rows is None:
        status = "unavailable"
        message = "龙虎榜数据请求失败，请稍后重新检查"
    elif official_stock_count == 0 and detail_count == 0:
        status = "empty"
        message = "该交易日没有股票上榜"
    elif official_stock_count > 0 and (not detail_available or detail_count == 0):
        status = "detail_missing"
        message = f"当日有 {official_stock_count} 只股票上榜，但席位明细尚未返回"
    elif detail_count > 0 and recognized_count == 0:
        status = "unclassified"
        message = f"当日有 {official_stock_count} 只股票上榜，席位均待归类"
    else:
        status = "success" if not unclassified else "partial"
        message = "部分营业部尚未归类" if unclassified else ""
    return {
        "trade_date": trade_date,
        "status": status,
        "message": message,
        "summary": {
            "official_stock_count": official_stock_count,
            "trader_count": len(traders),
            "operation_count": len(operations),
            "unclassified_count": len(unclassified),
            "net_million": round(
                sum(float(item["net_million"]) for item in operations), 2
            ),
            "profile_count": len(profile_items),
        },
        "traders": traders,
        "operations": sorted(
            operations, key=lambda item: abs(float(item["net_million"])), reverse=True
        ),
        "unclassified_seats": unclassified,
        "profiles": profile_items,
    }


def _stock_context(rows: tuple[dict[str, Any], ...]) -> dict[str, dict[str, Any]]:
    result = {}
    for row in rows:
        identifier = str(row.get("ts_code") or "")
        if identifier and identifier not in result:
            result[identifier] = {
                "name": _text(row.get("name")),
                "change": _optional_number(row.get("pct_change")),
                "reason": _text(row.get("reason")),
            }
    return result


def _official_operations(
    rows: tuple[dict[str, Any], ...],
    profile_map: dict[str, dict[str, Any]],
    stocks: dict[str, dict[str, Any]],
) -> list[dict[str, Any]]:
    result = []
    for row in rows:
        identifier = str(row.get("ts_code") or "")
        trader = _text(row.get("hm_name")) or "未命名游资"
        profile = profile_map.get(trader) or {}
        seat = _text(row.get("hm_orgs")) or ""
        stock = stocks.get(identifier) or {}
        result.append(
            _operation(
                identifier=identifier,
                name=_text(row.get("ts_name")) or str(stock.get("name") or ""),
                change=stock.get("change"),
                reason=str(stock.get("reason") or ""),
                seat_name=seat or "未提供营业部",
                trader_name=trader,
                description=str(profile.get("description") or ""),
                buy=_number(row.get("buy_amount")) / 1_000_000,
                sell=_number(row.get("sell_amount")) / 1_000_000,
                net=_number(row.get("net_amount")) / 1_000_000,
                recognized=True,
            )
        )
    return result


def _seat_operation(
    row: dict[str, Any],
    stocks: dict[str, dict[str, Any]],
    aliases: dict[str, str],
    organization_map: dict[str, str],
) -> dict[str, Any]:
    identifier = str(row.get("ts_code") or "")
    seat = _text(row.get("exalter")) or "未命名营业部"
    trader = aliases.get(seat) or organization_map.get(seat) or ""
    stock = stocks.get(identifier) or {}
    buy = _number(row.get("buy")) / 1_000_000
    sell = _number(row.get("sell")) / 1_000_000
    net = _number(row.get("net_buy")) / 1_000_000
    if net == 0 and (buy or sell):
        net = buy - sell
    return _operation(
        identifier=identifier,
        name=str(stock.get("name") or ""),
        change=stock.get("change"),
        reason=_text(row.get("reason")) or str(stock.get("reason") or ""),
        seat_name=seat,
        trader_name=trader,
        description="",
        buy=buy,
        sell=sell,
        net=net,
        recognized=bool(trader),
    )


def _operation(
    *,
    identifier: str,
    name: str,
    change: float | None,
    reason: str,
    seat_name: str,
    trader_name: str,
    description: str,
    buy: float,
    sell: float,
    net: float,
    recognized: bool,
) -> dict[str, Any]:
    return {
        "identifier": identifier,
        "code": identifier.split(".")[0],
        "name": name,
        "change": change,
        "direction": "买入" if net > 0 else "卖出" if net < 0 else "持平",
        "buy_million": round(buy, 2),
        "sell_million": round(sell, 2),
        "net_million": round(net, 2),
        "seat_name": seat_name,
        "trader_name": trader_name,
        "description": description,
        "reason": reason,
        "recognized": recognized,
    }


def _aggregate_traders(
    operations: list[dict[str, Any]], profile_map: dict[str, dict[str, Any]]
) -> list[dict[str, Any]]:
    groups: dict[str, dict[str, Any]] = {}
    for operation in operations:
        name = str(operation.get("trader_name") or "")
        if not operation.get("recognized") or not name:
            continue
        group = groups.setdefault(
            name,
            {
                "name": name,
                "description": str((profile_map.get(name) or {}).get("description") or ""),
                "buy_million": 0.0,
                "sell_million": 0.0,
                "net_million": 0.0,
                "seats": set(),
                "stocks": set(),
                "operations": [],
            },
        )
        group["buy_million"] += float(operation["buy_million"])
        group["sell_million"] += float(operation["sell_million"])
        group["net_million"] += float(operation["net_million"])
        group["seats"].add(str(operation["seat_name"]))
        group["stocks"].add(str(operation["code"]))
        group["operations"].append(operation)
    result = []
    for group in groups.values():
        result.append(
            {
                "name": group["name"],
                "description": group["description"],
                "buy_million": round(group["buy_million"], 2),
                "sell_million": round(group["sell_million"], 2),
                "net_million": round(group["net_million"], 2),
                "seat_count": len(group["seats"]),
                "stock_count": len(group["stocks"]),
                "operation_count": len(group["operations"]),
                "operations": sorted(
                    group["operations"],
                    key=lambda item: abs(float(item["net_million"])),
                    reverse=True,
                ),
            }
        )
    return sorted(result, key=lambda item: abs(float(item["net_million"])), reverse=True)


def _aggregate_unclassified(operations: list[dict[str, Any]]) -> list[dict[str, Any]]:
    groups: dict[str, dict[str, Any]] = {}
    for operation in operations:
        if operation.get("recognized"):
            continue
        seat = str(operation["seat_name"])
        group = groups.setdefault(
            seat, {"seat_name": seat, "net_million": 0.0, "operation_count": 0}
        )
        group["net_million"] += float(operation["net_million"])
        group["operation_count"] += 1
    result = [
        {**group, "net_million": round(float(group["net_million"]), 2)}
        for group in groups.values()
    ]
    return sorted(result, key=lambda item: abs(float(item["net_million"])), reverse=True)


def _organizations(value: Any) -> list[str]:
    text = _text(value)
    parsed: Any = None
    if text.startswith("["):
        try:
            parsed = json.loads(text)
        except json.JSONDecodeError:
            parsed = None
    values = parsed if isinstance(parsed, list) else re.split(r"[,，;；\n]+", text)
    return list(dict.fromkeys(_text(item) for item in values if _text(item)))


def _text(value: Any) -> str:
    return str(value or "").strip()


def _number(value: Any) -> float:
    try:
        number = float(value)
        return number if number == number else 0.0
    except (TypeError, ValueError):
        return 0.0


def _optional_number(value: Any) -> float | None:
    if value in (None, ""):
        return None
    return _number(value)
