"""Prose-free historical event and view construction."""

from __future__ import annotations

import hashlib
import json
import re
from collections import defaultdict
from datetime import date, timedelta
from pathlib import Path
from typing import Final, Literal

from checkin_cli.history_baseline import HistoryBaselineParser, SourceInventory
from checkin_cli.history_models import Baseline, BaselineEntry
from checkin_cli.models import ContractCheckin, ContractStatus, Event, EventType, ImportManifest, Provenance


KST_SUFFIX: Final[str] = "T08:00:00+09:00"
HISTORY_HEADING: Final[re.Pattern[str]] = re.compile(r"^##\s+(D\+\d+\b[^\n]*)", re.MULTILINE)


def parse_baseline(manifest_path: Path) -> Baseline:
    """Verify the approved source inventory then extract only permitted fields."""
    return HistoryBaselineParser(SourceInventory.from_manifest(manifest_path)).parse()


def legacy_history_events(source: Path, source_sha256: str, recorded_at: str) -> tuple[Event, ...]:
    """Produce legacy heading-only provenance events without retaining source text."""
    anchors = tuple(match.group(1).strip() for match in HISTORY_HEADING.finditer(source.read_text(encoding="utf-8")))
    return tuple(
        Event(
            event_id=f"history_{_digest(source_sha256 + anchor)[:24]}",
            event_type=EventType.HISTORY_IMPORTED,
            schema_version="1.0",
            occurred_at_kst=recorded_at,
            recorded_at_kst=recorded_at,
            provenance=Provenance(source_type="historical_markdown", source_ref=str(source), content_sha256=source_sha256, section_anchor=anchor),
            status=ContractStatus.ARCHIVED,
        )
        for anchor in anchors
    )


def baseline_events(
    baseline: Baseline,
    recorded_at: str,
    *,
    schema_version: Literal["1.0", "2.0"] = "1.0",
) -> tuple[Event, ...]:
    """Create deterministic archived events with only known historical metrics."""
    return tuple(_baseline_event(entry, recorded_at, schema_version=schema_version) for entry in baseline.entries)


def baseline_report(baseline: Baseline) -> str:
    """Serialize audit coverage without source prose."""
    return json.dumps(baseline.to_storage_dict(), ensure_ascii=False, sort_keys=True) + "\n"


def historical_view(events: tuple[Event, ...], baseline: Baseline) -> str:
    """Render dated historical measurements separately from live current state."""
    observations = tuple(
        {
            "date": event.occurred_at_kst[:10],
            "weight_kg": event.check_in.body_weight_kg,
            "sleep_hours": event.check_in.sleep_hours,
            "calories_kcal": event.check_in.calories_kcal,
        }
        for event in events
        if event.event_type is EventType.HISTORY_IMPORTED and event.check_in is not None
    )
    return json.dumps(
        {
            "coverage": baseline.coverage.to_storage_dict(),
            "observations": observations,
            "weekly_weight_trend": _weekly_weight_trend(observations),
            "reconciliation": baseline.reconciliation.to_storage_dict(),
        },
        ensure_ascii=False,
        sort_keys=True,
    ) + "\n"


def legacy_manifest(source: Path, source_sha256: str, range_label: str) -> str:
    """Serialize old heading import provenance without historical body text."""
    anchors = tuple(match.group(1).strip() for match in HISTORY_HEADING.finditer(source.read_text(encoding="utf-8")))
    return json.dumps({"source_path": str(source), "sha256": source_sha256, "range_label": range_label, "section_anchors": anchors}, ensure_ascii=False, sort_keys=True) + "\n"


def _baseline_event(
    entry: BaselineEntry,
    recorded_at: str,
    *,
    schema_version: Literal["1.0", "2.0"] = "1.0",
) -> Event:
    check_in = ContractCheckin(
        body_weight_kg=entry.weight_kg,
        sleep_hours=entry.sleep_hours,
        calories_kcal=entry.calories_kcal,
        training_summary=",".join(entry.training_tokens) or None,
        digestion_summary=entry.digestion_status,
        pain_summary=",".join(entry.pain_tokens) or None,
    )
    payload = check_in.model_dump(exclude_none=True)
    event_identity = entry.source_sha256 + entry.section_anchor + json.dumps(payload, sort_keys=True)
    if schema_version == "2.0":
        event_identity = schema_version + event_identity
    event_id = f"history_{_digest(event_identity)[:24]}"
    return Event(
        event_id=event_id,
        event_type=EventType.HISTORY_IMPORTED,
        schema_version=schema_version,
        occurred_at_kst=f"{entry.recorded_date}{KST_SUFFIX}",
        recorded_at_kst=recorded_at,
        provenance=Provenance(source_type="historical_markdown", source_ref=entry.source_path, content_sha256=entry.source_sha256, section_anchor=entry.section_anchor),
        status=ContractStatus.ARCHIVED,
        check_in=check_in,
        import_manifest=(
            ImportManifest(observation_kst_day=entry.recorded_date)
            if schema_version == "2.0"
            else None
        ),
    )


def _weekly_weight_trend(observations: tuple[dict[str, str | float | int | None], ...]) -> tuple[dict[str, str | float | int], ...]:
    """Group observed weights by Monday without inventing missing-day values."""
    weights: dict[str, list[float]] = defaultdict(list)
    for observation in observations:
        value = observation["weight_kg"]
        if value is not None:
            observed_date = date.fromisoformat(str(observation["date"]))
            week_start = observed_date - timedelta(days=observed_date.weekday())
            weights[week_start.isoformat()].append(float(value))
    return tuple(
        {"week_start": week_start, "sample_count": len(values), "average_weight_kg": round(sum(values) / len(values), 3)}
        for week_start, values in sorted(weights.items())
    )


def _digest(value: str) -> str:
    return hashlib.sha256(value.encode()).hexdigest()
