"""Manning input/output adaptation; hydraulic formulas remain in core."""

from __future__ import annotations

import math
import re
from typing import Any, Callable

from . import core


HYDRAULIC_COLUMNS = {
    "水位(m)": "water_level_m",
    "水深(m)": "depth_m",
    "过水面积(m²)": "area_m2",
    "湿周(m)": "wetted_perimeter_m",
    "水力半径(m)": "hydraulic_radius_m",
    "水面宽(m)": "top_width_m",
    "平均水深(m)": "hydraulic_depth_m",
    "平均流速(m/s)": "velocity_ms",
    "弗汝德数": "froude_number",
    "流量(m³/s)": "discharge_m3s",
    "深泓点横距(m)": "thalweg_x_m",
    "深泓点高程(m)": "thalweg_elevation_m",
}


def optional_number(value: Any, label: str) -> float | None:
    if value is None or isinstance(value, str) and not value.strip():
        return None
    try:
        number = float(value)
    except (TypeError, ValueError) as exc:
        raise core.HydrologyError(f"{label}必须是数字") from exc
    if isinstance(value, bool) or not math.isfinite(number):
        raise core.HydrologyError(f"{label}必须是有限数字")
    return number


def positive_number(value: Any, label: str) -> float:
    number = optional_number(value, label)
    if number is None or number <= 0:
        raise core.HydrologyError(f"{label}必须大于 0；留空与 0 不等同")
    return number


def parse_section_points(raw: Any) -> tuple[tuple[float, float], ...]:
    if isinstance(raw, str):
        text = raw.strip().lstrip("\ufeff").translate(str.maketrans("，；｜", ",;|"))
        records = [part.strip() for part in re.split(r"[;|\r\n]+", text) if part.strip()]
        records = [re.split(r"[,\t ]+", row) for row in records]
        if records and len(records[0]) == 2:
            x_header, z_header = records[0]
            if (re.fullmatch(r"(?:横距|起点距|里程|距离|x)(?:[（(]m[）)])?", x_header, re.I)
                    and re.fullmatch(r"(?:高程|高程z|z)(?:[（(]m[）)])?", z_header, re.I)):
                records = records[1:]
    elif isinstance(raw, (list, tuple)):
        records = raw
    else:
        raise core.HydrologyError("断面点应为横距、高程两列数据")
    points = []
    for index, record in enumerate(records, 1):
        if isinstance(record, dict):
            record = (record.get("x"), record.get("z"))
        if not isinstance(record, (list, tuple)) or len(record) != 2:
            raise core.HydrologyError(f"断面点第 {index} 行必须恰有横距、高程两列")
        x = optional_number(record[0], f"断面点第 {index} 行横距")
        z = optional_number(record[1], f"断面点第 {index} 行高程")
        if x is None or z is None:
            raise core.HydrologyError(f"断面点第 {index} 行横距、高程不能留空")
        points.append((x, z))
    if len(points) < 2:
        raise core.HydrologyError("断面点至少需要 2 个")
    if len(points) > 10000:
        raise core.HydrologyError("单断面最多支持 10000 个点")
    if len({x for x, _z in points}) != len(points):
        raise core.HydrologyError("断面点起点距不能重复；当前底层模型不支持重复横距的垂直边墙")
    return tuple(sorted(points))


def parse_frequency_flows(raw: Any) -> list[dict[str, Any]]:
    if raw is None or isinstance(raw, str) and not raw.strip():
        return []
    records = []
    if isinstance(raw, str):
        text = raw.translate(str.maketrans("：；％，", ":;%,"))
        for index, token in enumerate(re.split(r"[;\r\n]+", text), 1):
            if not token.strip():
                continue
            parts = re.split(r"[:\t,]", token.strip())
            if len(parts) != 2:
                raise core.HydrologyError(f"频率流量第 {index} 项应为 1%:650 或 100年:650；旧 100:650 仍指100年")
            label, flow = (part.strip() for part in parts)
            percent = re.fullmatch(r"(?:P\s*=\s*)?([+\d.eE-]+)\s*%", label, re.I)
            years = re.fullmatch(r"(?:T\s*=\s*)?([+\d.eE-]+)\s*(?:年(?:一遇)?)?", label, re.I)
            if percent:
                records.append({"frequency_percent": percent[1], "discharge_m3s": flow})
            elif years:
                records.append({"return_period_years": years[1], "discharge_m3s": flow})
            else:
                raise core.HydrologyError(f"频率流量第 {index} 项须明确百分数(%)或重现期(年)")
    elif isinstance(raw, dict):
        return parse_frequency_flows(";".join(f"{key}:{value}" for key, value in raw.items()))
    elif isinstance(raw, (list, tuple)):
        records = list(raw)
    else:
        raise core.HydrologyError("频率流量须为旧文本草稿或带明确频率单位的数据行")
    if len(records) > 100:
        raise core.HydrologyError("各频率设计流量最多支持 100 项")
    result = []
    for index, record in enumerate(records, 1):
        if not isinstance(record, dict):
            raise core.HydrologyError(f"频率流量第 {index} 项须为数据行")
        has_percent = optional_number(record.get("frequency_percent"), "频率百分数") is not None
        has_years = optional_number(record.get("return_period_years"), "重现期") is not None
        if has_percent == has_years:
            raise core.HydrologyError(f"频率流量第 {index} 项须且只能指定 frequency_percent 或 return_period_years")
        flow = positive_number(record.get("discharge_m3s", record.get("flow_m3s")), f"频率流量第 {index} 项流量")
        if has_percent:
            percent = positive_number(record["frequency_percent"], "频率百分数")
            if percent > 100:
                raise core.HydrologyError("频率百分数必须在 0 至 100% 之间（不含 0）")
            years = 100 / percent
            label = f"P={percent:g}%"
        else:
            years = positive_number(record["return_period_years"], "重现期")
            if years < 1:
                raise core.HydrologyError("年最大值频率的重现期不能小于 1 年")
            percent = 100 / years
            label = f"{years:g}年一遇"
        result.append({"label": label, "kind": "frequency", "frequency_kind": "percent" if has_percent else "return_period",
                       "frequency_percent": percent, "return_period_years": years, "discharge_m3s": flow})
    return result


def hydraulic_record(points: tuple, level: float, roughness_n: float, slope: float) -> dict[str, float]:
    q, area, wetted, radius, width, depth = core.manning_flow_for_level(points, level, roughness_n, slope)
    velocity = q / area if area > 0 else 0.0
    hydraulic_depth = area / width if width > 0 else 0.0
    thalweg_x, thalweg_z = min(points, key=lambda p: p[1])
    return {"water_level_m": level, "discharge_m3s": q, "depth_m": depth, "area_m2": area,
            "wetted_perimeter_m": wetted, "hydraulic_radius_m": radius, "top_width_m": width,
            "hydraulic_depth_m": hydraulic_depth, "velocity_ms": velocity,
            "froude_number": velocity / math.sqrt(9.80665 * hydraulic_depth) if hydraulic_depth > 0 else 0.0,
            "thalweg_x_m": thalweg_x, "thalweg_elevation_m": thalweg_z}


def hydraulic_row(record: dict[str, Any]) -> dict[str, str]:
    return {label: f"{record[key]:.3f}" for label, key in HYDRAULIC_COLUMNS.items()}


def rating_curve_for_section(points: tuple, roughness_n: float, slope: float, design_level: float | None,
                             level_step_m: float | None = None, maximum_depth_m: float | None = None,
                             target_levels: tuple[float, ...] = ()) -> tuple[list[dict], list[dict]]:
    points = parse_section_points(points)
    roughness_n = positive_number(roughness_n, "糙率 n")
    slope = positive_number(slope, "水力坡降 I")
    step = 0.2 if level_step_m is None else positive_number(level_step_m, "水位计算步长")
    if not 0.01 <= step <= 5:
        raise core.HydrologyError("水位计算步长应在 0.01 至 5.00 m 之间")
    low = min(z for _x, z in points)
    levels = [positive for value in (design_level, *target_levels)
              if (positive := optional_number(value, "目标水位")) is not None]
    if any(level < low for level in levels):
        raise core.HydrologyError("目标水位不能低于断面最低高程")
    top = (low + positive_number(maximum_depth_m, "最大水深") if maximum_depth_m is not None
           else max([z for _x, z in points] + levels) + step)
    top = max([top, *levels])
    count = math.floor((top - low) / step)
    if count > 2000:
        raise core.HydrologyError("水位流量表超过 2000 行，请增大步长或减小最大水深")
    # Always retain the exact top and target levels, even with a coarse step.
    grid = {round(low + index * step, 10) for index in range(count + 1)}
    grid.update([low, top, *levels])
    grid = sorted(z for z in grid if low <= z <= top)
    if len(grid) > 2000:
        raise core.HydrologyError("水位流量表超过 2000 行，请增大步长或减小最大水深")
    curve = [hydraulic_record(points, level, roughness_n, slope) for level in grid]
    return [hydraulic_row(row) for row in curve], curve


def build_manning_output(params: dict[str, Any], points: tuple, rating_builder: Callable) -> dict[str, Any]:
    name = str(params.get("name") or "设计断面").strip()
    roughness = positive_number(params.get("roughness_n", 0.035), "糙率 n")
    slope = positive_number(params.get("slope", 0.001), "水力坡降 I")
    mode = str(params.get("manning_mode") or "auto")
    if mode not in {"auto", "curve", "design"}:
        raise core.HydrologyError("曼宁计算模式应为 auto、curve 或 design")
    # An explicitly empty primary key must not resurrect a stale legacy alias.
    flow = None if mode == "curve" else optional_number(params.get("design_flow_m3s", params.get("flow_m3s")), "设计流量")
    if flow is not None:
        flow = positive_number(flow, "设计流量")
    frequencies = [] if mode == "curve" else parse_frequency_flows(params.get("frequency_flows"))
    if mode == "design" and flow is None and not frequencies:
        raise core.HydrologyError("设计反算模式至少填写一个设计流量或频率流量")
    step = optional_number(params.get("rating_step_m"), "水位计算步长")
    step = 0.2 if step is None else step
    if not 0.01 <= step <= 5:
        raise core.HydrologyError("水位计算步长应在 0.01 至 5.00 m 之间")
    maximum_depth = optional_number(params.get("rating_max_depth_m"), "最大水深")
    if maximum_depth is not None:
        maximum_depth = positive_number(maximum_depth, "最大水深")
    headroom = optional_number(params.get("rating_headroom_m"), "曲线余高")
    if headroom is not None and headroom < 0:
        raise core.HydrologyError("曲线余高不能小于 0")
    markers = []
    targets = ([{"label": "设计流量", "kind": "design", "discharge_m3s": flow}] if flow is not None else []) + frequencies
    for target in targets:
        result = core.calculate_manning_water_level(core.ManningSectionInput(name, roughness, slope, target["discharge_m3s"], points))
        record = hydraulic_record(points, result.water_level, roughness, slope)
        record.update({"depth_m": result.max_depth_m, "area_m2": result.area_m2,
                       "wetted_perimeter_m": result.wetted_perimeter_m, "hydraulic_radius_m": result.hydraulic_radius_m,
                       "top_width_m": result.top_width_m, "velocity_ms": result.velocity_ms, "froude_number": result.froude_number,
                       "calculated_flow_m3s": result.calculated_flow_m3s})
        record["hydraulic_depth_m"] = result.area_m2 / result.top_width_m if result.top_width_m else 0.0
        markers.append({**record, **target})
    low = min(z for _x, z in points)
    highest = max((m["water_level_m"] for m in markers), default=None)
    effective_headroom = (max(0.5, (highest - low) * 0.15) if headroom is None else headroom) if markers else None
    chart_top = highest + effective_headroom if markers else None
    default_top = max(max(z for _x, z in points), highest if highest is not None else low) + step
    table_top = low + maximum_depth if maximum_depth is not None else default_top
    table_top = max(table_top, chart_top if chart_top is not None else table_top)
    target_levels = tuple(m["water_level_m"] for m in markers)
    rating_rows, rating_curve = rating_builder(points, roughness, slope, highest, step, table_top - low, target_levels)
    if markers:
        # Plot resolution is independent of the complete engineering-table step.
        chart_levels = {low + (chart_top - low) * index / 160 for index in range(161)}
        chart_levels.update([low, chart_top, *target_levels])
        chart_curve = [hydraulic_record(points, z, roughness, slope) for z in sorted(chart_levels)]
    else:
        chart_curve = list(rating_curve)
        chart_top = table_top
    design = next((m for m in markers if m["kind"] == "design"), None)
    frequency_rows = []
    for marker in markers:
        if marker["kind"] == "frequency":
            frequency_rows.append({"频率标识": marker["label"], "频率(%)": f"{marker['frequency_percent']:g}",
                                   "重现期": f"{marker['return_period_years']:g}年一遇", **hydraulic_row(marker),
                                   "最大水深(m)": f"{marker['depth_m']:.3f}",
                                   "复核流量(m³/s)": f"{marker['calculated_flow_m3s']:.3f}"})
    metrics = []
    if design:
        for label, key, unit in [("设计水位", "water_level_m", "m"), ("过水面积", "area_m2", "m²"),
                                 ("湿周", "wetted_perimeter_m", "m"), ("水力半径", "hydraulic_radius_m", "m"),
                                 ("水面宽", "top_width_m", "m"), ("最大水深", "depth_m", "m"),
                                 ("平均水深", "hydraulic_depth_m", "m"), ("平均流速", "velocity_ms", "m/s"),
                                 ("弗汝德数", "froude_number", ""), ("深泓点横距", "thalweg_x_m", "m"),
                                 ("深泓点高程", "thalweg_elevation_m", "m"), ("复核流量", "calculated_flow_m3s", "m³/s")]:
            metrics.append({"name": label, "value": f"{design[key]:.3f}", "unit": unit, "note": ""})
    else:
        metrics = [{"name": label, "value": value, "unit": unit, "note": ""} for label, value, unit in
                   [("断面最低高程", low, "m"), ("关系表最高水位", table_top, "m"),
                    ("水位步长", step, "m"), ("关系表行数", len(rating_rows), ""), ("频率水位数量", len(markers), "")]]
    tables = []
    if design:
        tables.append({"title": "设计水位水力要素表", "columns": ["项目", "数值", "单位", "说明"],
                       "rows": [{"项目": row["name"], "数值": row["value"], "单位": row["unit"], "说明": row["note"]} for row in metrics]})
    tables.append({"title": "水位流量关系计算表", "columns": list(HYDRAULIC_COLUMNS), "rows": rating_rows})
    if frequency_rows:
        tables.append({"title": "各频率设计流量对应水位", "columns": list(frequency_rows[0]), "rows": frequency_rows})
    tables.append({"title": "输入断面点表", "columns": ["横距(m)", "高程(m)"],
                   "rows": [{"横距(m)": x, "高程(m)": z} for x, z in points]})
    source = "曼宁公式 Q=(1/n)·A·R^(2/3)·I^(1/2)；R=A/P，V=Q/A，平均水深=A/B，Fr=V/√(9.80665·A/B)。"
    notes = ["采用全断面单一糙率；未实现分段糙率分区输水能力叠加。", "无设计流量时仅计算关系表，不生成虚构设计水位。"]
    if markers:
        notes.append(f"曲线只计算至最高设计/频率水位以上 {effective_headroom:g} m；完整关系表不按图区裁剪。")
    bank_limit = min(points[0][1], points[-1][1])
    if table_top > bank_limit:
        notes.append("部分水位高于断面端点高程，沿用原模型的有限断面计算，不自动延伸岸坡；须补测或核验漫溢边界。")
    book = ["单断面曼宁公式水位计算书", "", "一、公式与计算口径", source, *notes,
            "", "二、输入参数", f"断面：{name}；n={roughness:g}；I={slope:g} m/m；水位步长={step:g} m。",
            f"设计Q={'未指定' if flow is None else str(flow) + ' m³/s'}；表格最高水位={table_top:.3f} m；曲线最高水位={chart_top:.3f} m。",
            "", "三、设计及频率水位反算与代入"]
    for m in markers:
        book.append(f"{m['label']}：Q目标={m['discharge_m3s']:g} m³/s，原求解器二分反算 Z={m['water_level_m']:.3f} m；"
                    f"Q=(1/{roughness:g})×{m['area_m2']:.3f}×{m['hydraulic_radius_m']:.3f}^(2/3)×{slope:g}^(1/2)"
                    f"≈{m['calculated_flow_m3s']:.3f} m³/s（显示值已舍入）。")
    book.extend(["", "四、水力要素与输入断面", "五、全断面水位流量关系表"])
    for table in tables:
        columns = table["columns"]
        book.extend(["", table["title"], " | ".join(columns)])
        book.extend(" | ".join(str(row.get(column, "")) for column in columns) for row in table["rows"])
    profile_marker = design or (max(markers, key=lambda m: m["water_level_m"]) if markers else None)
    thalweg_x, thalweg_z = min(points, key=lambda p: p[1])
    profile = {"name": name, "points": [{"x": x, "z": z} for x, z in points],
               "thalweg_x": thalweg_x, "thalweg_z": thalweg_z}
    if profile_marker:
        profile.update({"water_level": profile_marker["water_level_m"], "design_flow_m3s": profile_marker["discharge_m3s"],
                        "velocity_ms": profile_marker["velocity_ms"], "area_m2": profile_marker["area_m2"]})
    return {"program_id": "4-02", "title": "单断面曼宁公式水位", "source": source,
            "manning_mode": "design" if markers else "curve", "metrics": metrics, "tables": tables,
            "rating_curve": rating_curve, "chart_rating_curve": chart_curve, "rating_markers": markers,
            "rating_marker": design or (markers[0] if markers else None),
            "rating_chart_bounds": {"min_water_level_m": low, "max_water_level_m": chart_top,
                                    "highest_design_water_level_m": highest, "headroom_m": effective_headroom},
            "rating_table_max_water_level_m": table_top, "water_level": design["water_level_m"] if design else None,
            "section_profile": profile, "notes": notes, "calculation_book_text": "\n".join(book)}
