"""JRA レース結果ページ（accessS.html）の解析"""
from __future__ import annotations

import re
from dataclasses import dataclass, field
from decimal import Decimal
from typing import Any

from bs4 import BeautifulSoup


@dataclass
class ParsedRace:
    race_date: str | None = None
    kai: int | None = None
    nichi: int | None = None
    race_number: int | None = None
    track_name: str | None = None
    race_name: str | None = None
    start_time: str | None = None
    weather: str | None = None
    going_turf: str | None = None
    going_dirt: str | None = None
    surface: str | None = None
    direction: str | None = None
    distance_m: int | None = None
    age_condition: str | None = None
    result_rows: list[dict[str, Any]] = field(default_factory=list)


def _norm_space(s: str) -> str:
    return re.sub(r"\s+", " ", s.replace("\u3000", " ")).strip()


def parse_race_result_html(html: str) -> ParsedRace | None:
    if "パラメータエラー" in html or "Forbidden" in html:
        return None
    soup = BeautifulSoup(html, "html.parser")
    title_el = None
    for h in soup.find_all("h1"):
        tx = _norm_space(h.get_text(" ", strip=True))
        if tx.startswith("レース結果"):
            title_el = h
            break
    if not title_el:
        title_el = soup.find("title")
    if not title_el:
        return None
    h1 = _norm_space(title_el.get_text(" ", strip=True))
    if "レース結果" not in h1:
        return None

    out = ParsedRace()

    # # レース結果2021年8月1日（日曜）3回新潟4日 1レース
    m = re.search(
        r"レース結果\s*(\d{4})年\s*(\d{1,2})月\s*(\d{1,2})日",
        h1,
    )
    if m:
        out.race_date = f"{int(m.group(1)):04d}-{int(m.group(2)):02d}-{int(m.group(3)):02d}"

    m2 = re.search(r"(\d+)回\s*(.+?)\s*(\d+)日\s*(\d+)\s*レース", h1)
    if m2:
        out.kai = int(m2.group(1))
        out.track_name = m2.group(2).strip()
        out.nichi = int(m2.group(3))
        out.race_number = int(m2.group(4))

    # 発走時刻 / 天候 / 馬場
    # DOM上は <ul> 配下の <li><span>天候</span><span>曇</span> のような構造になっていることがあるため、
    # テキストを element 単位で集めて判定する。
    for li in soup.find_all("li"):
        t = _norm_space(li.get_text(" ", strip=True))
        if "発走時刻" in t:
            m = re.search(r"(\d{1,2})時\s*(\d{1,2})分", t)
            if m:
                out.start_time = f"{int(m.group(1)):02d}:{int(m.group(2)):02d}:00"
        if t.startswith("天候"):
            # 例: "天候 曇"
            out.weather = t.replace("天候", "", 1).strip() or out.weather
        if t.startswith("芝"):
            # 例: "芝 良"
            out.surface = out.surface or "芝"
            out.going_turf = t.replace("芝", "", 1).strip() or out.going_turf
        if t.startswith("ダ"):
            out.surface = out.surface or "ダ"
            out.going_dirt = t.replace("ダ", "", 1).strip() or out.going_dirt

    if not out.start_time:
        # 改行が挟まる場合があるため全テキストから拾う
        m = re.search(r"発走時刻[^0-9]*(\d{1,2})時\s*(\d{1,2})分", soup.get_text("\n"))
        if m:
            out.start_time = f"{int(m.group(1)):02d}:{int(m.group(2)):02d}:00"

    # ページ上部にも h2 があるため、タイトル(h1)直後のものを採用する
    h2 = title_el.find_next("h2") if hasattr(title_el, "find_next") else soup.find("h2")
    if h2:
        out.race_name = _norm_space(h2.get_text(" ", strip=True))

    for p in soup.find_all("p"):
        t = _norm_space(p.get_text(" ", strip=True))
        m = re.search(r"コース[：:]\s*([\d,]+)メートル[（(]([^）)]+)[）)]", t)
        if m:
            out.distance_m = int(m.group(1).replace(",", ""))
            detail = m.group(2)
            if "芝" in detail and not out.surface:
                out.surface = "芝"
            if "ダ" in detail and not out.surface:
                out.surface = "ダ"
            if "右" in detail:
                out.direction = "右"
            elif "左" in detail:
                out.direction = "左"
            elif "直線" in detail:
                out.direction = "直線"
            break

    # 結果テーブル（着順ヘッダ）
    for table in soup.find_all("table"):
        heads = [ _norm_space(th.get_text(" ", strip=True)) for th in table.find_all("th") ]
        if not heads or "着順" not in heads[0] and "着順" not in "".join(heads):
            continue
        rows = []
        for tr in table.find_all("tr"):
            cells = tr.find_all("td")
            if len(cells) < 8:
                continue
            try:
                pos_txt = _norm_space(cells[0].get_text(" ", strip=True))
                if not pos_txt.isdigit():
                    continue
                finish = int(pos_txt)
            except (ValueError, IndexError):
                continue
            bracket = _norm_space(cells[1].get_text(" ", strip=True))
            if not bracket:
                img = cells[1].find("img")
                if img:
                    alt = _norm_space(img.get("alt") or "")
                    m = re.search(r"枠\s*(\d+)", alt)
                    if m:
                        bracket = m.group(1)
                    else:
                        src = img.get("src") or ""
                        m2 = re.search(r"/(\\d+)\\.png", src)
                        if m2:
                            bracket = m2.group(1)
            umaban = _norm_space(cells[2].get_text(" ", strip=True))
            horse_a = cells[3].find("a")
            horse_name = _norm_space((horse_a or cells[3]).get_text(" ", strip=True))
            time_cell = _norm_space(cells[7].get_text(" ", strip=True)) if len(cells) > 7 else ""
            margin = _norm_space(cells[8].get_text(" ", strip=True)) if len(cells) > 8 else ""
            corner = _norm_space(cells[9].get_text(" ", strip=True)) if len(cells) > 9 else ""
            last3 = _norm_space(cells[10].get_text(" ", strip=True)) if len(cells) > 10 else ""
            jockey = _norm_space(cells[6].get_text(" ", strip=True)) if len(cells) > 6 else ""
            trainer = _norm_space(cells[12].get_text(" ", strip=True)) if len(cells) > 12 else ""
            pop = _norm_space(cells[13].get_text(" ", strip=True)) if len(cells) > 13 else ""
            rows.append({
                "finish_position": finish,
                "bracket": int(bracket) if bracket.isdigit() else None,
                "horse_number": int(umaban) if umaban.isdigit() else None,
                "horse_name": horse_name,
                "jockey_name": jockey,
                "race_time_raw": time_cell,
                "margin": margin or None,
                "passing_order": corner or None,
                "last_3f": _parse_last3(last3),
                "trainer_name": trainer,
                "popularity": int(pop) if pop.isdigit() else None,
            })
        if rows:
            out.result_rows = rows
            out.head_count = len(rows)
            break

    if not out.race_date or not out.track_name or not out.result_rows:
        return None
    return out


def _parse_last3(s: str) -> Decimal | None:
    m = re.search(r"(\d{2}\.?\d?)", s)
    if not m:
        return None
    try:
        return Decimal(m.group(1))
    except Exception:
        return None
