"""Чтение markdown-таблиц волта: дневной ряд, тренировки, дневник питания."""

import re
from dataclasses import dataclass, replace
from datetime import date
from typing import Optional

DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")


@dataclass
class DayRow:
    day: date
    weight: Optional[float] = None
    fat_pct: Optional[float] = None
    lean: Optional[float] = None
    steps: Optional[int] = None
    exercise_min: Optional[int] = None
    hrv: Optional[float] = None
    resting_hr: Optional[int] = None
    sleep_h: Optional[float] = None
    awake_h: Optional[float] = None
    active_kcal: Optional[int] = None
    cycle_day: Optional[int] = None
    meds: Optional[str] = None


def _cells(line):
    if not line.strip().startswith("|"):
        return None
    return [c.strip() for c in line.strip().strip("|").split("|")]


def _num(raw, cast):
    if raw is None or raw in ("", "—", "-"):
        return None
    try:
        return cast(raw.replace(",", "."))
    except ValueError:
        return None


def _text(raw):
    if raw is None or raw in ("", "—", "-"):
        return None
    return raw


@dataclass
class Workout:
    day: date
    kind: str
    minutes: int
    avg_hr: Optional[int] = None

    @property
    def is_strength(self):
        return "силов" in self.kind.lower()


@dataclass
class Measurement:
    day: date
    weight: Optional[float] = None
    fat_pct: Optional[float] = None


def parse_body_composition(text):
    """Таблица замеров веса и состава тела → {date: Measurement}.

    Нужна потому, что health-sync кладёт вес в дневной ряд с задержкой,
    а свежий замер появляется здесь раньше.
    """
    rows = {}
    for line in text.splitlines():
        cells = _cells(line)
        if not cells or not DATE_RE.match(cells[0]) or len(cells) < 2:
            continue
        clean = [c.replace("*", "").replace("%", "").strip() for c in cells]
        day = date.fromisoformat(cells[0])
        rows[day] = Measurement(
            day=day,
            weight=_num(clean[1], float),
            fat_pct=_num(clean[2], float) if len(clean) > 2 else None,
        )
    return rows


def merge_weights(days, measurements):
    """Дополняет дневной ряд весом из замеров. Состав тела не переносится."""
    merged = dict(days)
    for day, measurement in measurements.items():
        if measurement.weight is None:
            continue
        row = merged.get(day)
        if row is None:
            merged[day] = DayRow(day=day, weight=measurement.weight)
        elif row.weight is None:
            merged[day] = replace(row, weight=measurement.weight)
    return merged


@dataclass
class FoodTotals:
    day: date
    kcal: Optional[float] = None
    protein: Optional[float] = None
    fat: Optional[float] = None
    carbs: Optional[float] = None
    estimated: bool = False


MONTHS = {
    "январ": 1, "феврал": 2, "март": 3, "апрел": 4, "ма": 5, "июн": 6,
    "июл": 7, "август": 8, "сентябр": 9, "октябр": 10, "ноябр": 11, "декабр": 12,
}

MONTH_HEADING_RE = re.compile(r"^##\s+([А-Яа-яЁё]+)\s+(\d{4})\s*$")
DAY_HEADING_RE = re.compile(r"^###\s+(\d{1,2})\s+([а-яё]+)")


def _month_number(word):
    word = word.lower()
    for stem, number in sorted(MONTHS.items(), key=lambda kv: -len(kv[0])):
        if word.startswith(stem):
            return number
    return None


def parse_food_diary(text):
    """Дневник питания → {date: FoodTotals}. Год берётся из заголовка месяца выше."""
    days = {}
    year = None
    current = None

    for line in text.splitlines():
        month_heading = MONTH_HEADING_RE.match(line)
        if month_heading:
            year = int(month_heading.group(2))
            current = None
            continue

        day_heading = DAY_HEADING_RE.match(line)
        if day_heading:
            month = _month_number(day_heading.group(2))
            current = (
                date(year, month, int(day_heading.group(1)))
                if year and month
                else None
            )
            continue

        if current is None or "Итого" not in line:
            continue

        cells = _cells(line)
        if not cells or len(cells) < 5:
            continue
        raw = [c.replace("*", "").strip() for c in cells[1:5]]
        values = [_num(c.lstrip("~"), float) for c in raw]
        days[current] = FoodTotals(
            day=current,
            kcal=values[0],
            protein=values[1],
            fat=values[2],
            carbs=values[3],
            estimated=any(c.startswith("~") for c in raw),
        )
        current = None

    return days


FRESHNESS_RE = re.compile(r"последний день с данными:\s*(\d{4}-\d{2}-\d{2})")


def parse_freshness(text):
    """Дата последнего дня с данными из шапки файла health-sync."""
    match = FRESHNESS_RE.search(text)
    return date.fromisoformat(match.group(1)) if match else None


def parse_workouts(text):
    """Таблица тренировок → [Workout]."""
    workouts = []
    for line in text.splitlines():
        cells = _cells(line)
        if not cells or not DATE_RE.match(cells[0]) or len(cells) < 3:
            continue
        minutes = _num(cells[2], int)
        if minutes is None:
            continue
        workouts.append(
            Workout(
                day=date.fromisoformat(cells[0]),
                kind=cells[1],
                minutes=minutes,
                avg_hr=_num(cells[3], int) if len(cells) > 3 else None,
            )
        )
    return workouts


def parse_daily_series(text):
    """Дневной ряд Apple Health → {date: DayRow}."""
    days = {}
    for line in text.splitlines():
        cells = _cells(line)
        if not cells or not DATE_RE.match(cells[0]):
            continue
        padded = cells + [None] * (13 - len(cells))
        days[date.fromisoformat(cells[0])] = DayRow(
            day=date.fromisoformat(cells[0]),
            weight=_num(padded[1], float),
            fat_pct=_num(padded[2], float),
            lean=_num(padded[3], float),
            steps=_num(padded[4], int),
            exercise_min=_num(padded[5], int),
            hrv=_num(padded[6], float),
            resting_hr=_num(padded[7], int),
            sleep_h=_num(padded[8], float),
            awake_h=_num(padded[9], float),
            active_kcal=_num(padded[10], int),
            cycle_day=_num(padded[11], int),
            meds=_text(padded[12]),
        )
    return days
