"""
Apple-Health-Schlaf-CSV → sleep_log (für Universal-Import und /api/sleep/import).
Nutzt dieselbe Logik wie der Sleep-Router, ohne HTTPException — arbeitet auf einem übergebenen Cursor.
"""

from __future__ import annotations

import csv
import io
import json
import logging
from datetime import date, datetime
from decimal import Decimal, InvalidOperation
from typing import Any, Literal

from dateutil import parser as dateutil_parser

logger = logging.getLogger(__name__)


def _strip_row_keys(row: dict) -> dict:
    return {(k or "").strip(): (v.strip() if isinstance(v, str) else v) for k, v in row.items()}


def _safe_float(value: Any) -> float | None:
    if value is None or value == "":
        return None
    if isinstance(value, (int, float)):
        return float(value)
    if isinstance(value, Decimal):
        return float(value)
    try:
        s = str(value).strip().replace(",", ".")
        return float(s)
    except (ValueError, InvalidOperation):
        return None


def _parse_apple_sleep_datetime(value: str) -> datetime:
    raw = (value or "").strip()
    if not raw:
        raise ValueError("empty datetime")
    fmts = (
        "%Y-%m-%d %H:%M:%S %z",
        "%d.%m.%y %H:%M:%S",
        "%d.%m.%Y %H:%M:%S",
        "%Y-%m-%d %H:%M:%S",
    )
    for fmt in fmts:
        try:
            return datetime.strptime(raw, fmt)
        except ValueError:
            continue
    try:
        return dateutil_parser.parse(raw, dayfirst=False)
    except (ValueError, TypeError, OverflowError) as e:
        raise ValueError(f"Unbekanntes Datumsformat: {raw!r}") from e


def _hr_to_minutes(hours: float | None) -> int:
    if hours is None:
        return 0
    return int(round(float(hours) * 60))


def detect_apple_sleep_csv_format(fieldnames: list[str] | None) -> Literal["segments", "summary"]:
    if not fieldnames:
        raise ValueError("CSV enthält keine Spaltenüberschriften.")
    fn = {(f or "").strip() for f in fieldnames}
    if {"Start", "End", "Duration (hr)", "Value"}.issubset(fn):
        return "segments"
    if "Start" in fn and "End" in fn and "Total Sleep (hr)" in fn:
        return "summary"
    raise ValueError(
        "Unbekanntes Apple-Health-Schlaf-CSV. Erwartet: Segment-Export oder Schlafanalyse-Zusammenfassung."
    )


def _build_nights_from_apple_summary(reader: csv.DictReader) -> dict[date, dict]:
    nights_dict: dict[date, dict] = {}
    for raw in reader:
        row = _strip_row_keys(raw)
        start_s = row.get("Start") or ""
        end_s = row.get("End") or ""
        if not start_s or not end_s:
            continue
        try:
            start_dt = _parse_apple_sleep_datetime(start_s)
            end_dt = _parse_apple_sleep_datetime(end_s)
        except ValueError:
            continue

        dt_key = (row.get("Date/Time") or row.get("Datum/Uhrzeit") or "").strip()
        if dt_key:
            try:
                wake_d = datetime.strptime(dt_key[:10], "%Y-%m-%d").date()
            except ValueError:
                wake_d = end_dt.date()
        else:
            wake_d = end_dt.date()

        core_hr = _safe_float(row.get("Core (hr)"))
        if core_hr is None:
            core_hr = _safe_float(row.get("Light (hr)")) or 0.0
        deep_min = _hr_to_minutes(_safe_float(row.get("Deep (hr)")))
        rem_min = _hr_to_minutes(_safe_float(row.get("REM (hr)")))
        light_min = _hr_to_minutes(core_hr)
        awake_min = _hr_to_minutes(_safe_float(row.get("Awake (hr)")))
        total_sleep_hr = _safe_float(row.get("Total Sleep (hr)"))
        nights_dict[wake_d] = {
            "bedtime": start_dt,
            "wake_time": end_dt,
            "segments": [],
            "deep_minutes": deep_min,
            "rem_minutes": rem_min,
            "light_minutes": light_min,
            "awake_minutes": awake_min,
            "total_sleep_hr": total_sleep_hr,
        }
    return nights_dict


def _build_nights_from_apple_segments(reader: csv.DictReader, phase_map: dict) -> dict[date, dict]:
    segments = []
    for raw in reader:
        row = _strip_row_keys(raw)
        phase_key = (row.get("Value") or "").strip()
        phase_en = phase_map.get(phase_key)
        if phase_en is None:
            continue
        try:
            start_dt = _parse_apple_sleep_datetime(row.get("Start") or "")
            end_dt = _parse_apple_sleep_datetime(row.get("End") or "")
            duration_hr = _safe_float(row.get("Duration (hr)"))
            if duration_hr is None:
                continue
        except (ValueError, TypeError):
            continue
        duration_min = int(duration_hr * 60)
        segments.append({
            "start": start_dt,
            "end": end_dt,
            "duration_min": duration_min,
            "phase": phase_en,
        })

    segments.sort(key=lambda s: s["start"])
    nights = []
    current_night = None

    for seg in segments:
        if current_night is None or (seg["start"] - current_night["wake_time"]).total_seconds() > 7200:
            current_night = {
                "bedtime": seg["start"],
                "wake_time": seg["end"],
                "segments": [],
                "deep_minutes": 0,
                "rem_minutes": 0,
                "light_minutes": 0,
                "awake_minutes": 0,
            }
            nights.append(current_night)

        current_night["segments"].append(seg)
        current_night["wake_time"] = max(current_night["wake_time"], seg["end"])
        current_night["bedtime"] = min(current_night["bedtime"], seg["start"])

        if seg["phase"] == "deep":
            current_night["deep_minutes"] += seg["duration_min"]
        elif seg["phase"] == "rem":
            current_night["rem_minutes"] += seg["duration_min"]
        elif seg["phase"] == "light":
            current_night["light_minutes"] += seg["duration_min"]
        elif seg["phase"] == "awake":
            current_night["awake_minutes"] += seg["duration_min"]

    nights_dict: dict[date, dict] = {}
    for night in nights:
        wake_date = night["wake_time"].date()
        nights_dict[wake_date] = night
    return nights_dict


def import_apple_sleep_nights(cur, profile_id: str, text: str) -> dict[str, Any]:
    """
    Schreibt in sleep_log. Kein conn.commit — Aufrufer rollt Transaktion.
    Gibt Statistik im Executor-Format zurück.
    """
    csv_text = text.replace("\r\n", "\n").replace("\r", "\n")
    if csv_text.startswith("\ufeff"):
        csv_text = csv_text[1:]
    reader = csv.DictReader(io.StringIO(csv_text))
    fmt = detect_apple_sleep_csv_format(reader.fieldnames)

    phase_map = {
        "Kern": "light",
        "Core": "light",
        "Light": "light",
        "REM": "rem",
        "Tief": "deep",
        "Deep": "deep",
        "Wach": "awake",
        "Awake": "awake",
        "Schlafend": None,
        "Asleep": None,
        "In Bed": None,
    }

    if fmt == "summary":
        nights_dict = _build_nights_from_apple_summary(reader)
    else:
        nights_dict = _build_nights_from_apple_segments(reader, phase_map)

    if not nights_dict:
        raise ValueError(
            "Keine importierbaren Schlafzeilen gefunden (prüfe Start/Ende und Format)."
        )

    inserted = 0
    updated = 0
    skipped = 0
    error_details: list[dict[str, Any]] = []
    affected_ids: list[str] = []

    row_hint = 0
    for wake_date, night in nights_dict.items():
        row_hint += 1
        phase_sum = (
            night["deep_minutes"] + night["rem_minutes"] + night["light_minutes"]
        )
        total_hr = night.get("total_sleep_hr")
        fallback_min = int(round(float(total_hr) * 60)) if total_hr is not None else 0
        duration_minutes = phase_sum if phase_sum > 0 else fallback_min
        if duration_minutes <= 0:
            logger.warning(
                "Sleep import: überspringe %s — Dauer 0 (Phasen-Summe und Total Sleep (hr) leer/0).",
                wake_date,
            )
            error_details.append(
                {
                    "row": row_hint,
                    "error": f"Schlafdauer für {wake_date} ist 0 — Phasen oder Total Sleep (hr) fehlen.",
                }
            )
            continue
        wake_count = sum(1 for seg in night["segments"] if seg["phase"] == "awake")

        sleep_segments = [
            {
                "phase": seg["phase"],
                "start": seg["start"].isoformat(),
                "end": seg["end"].isoformat(),
                "duration_min": seg["duration_min"],
            }
            for seg in night["segments"]
        ]

        cur.execute(
            """
            SELECT id, source FROM sleep_log
            WHERE profile_id = %s AND date = %s
            """,
            (profile_id, wake_date),
        )
        existing = cur.fetchone()

        if existing and existing["source"] == "manual":
            skipped += 1
            continue

        try:
            if existing:
                cur.execute(
                    """
                    UPDATE sleep_log SET
                        bedtime = %s,
                        wake_time = %s,
                        duration_minutes = %s,
                        wake_count = %s,
                        deep_minutes = %s,
                        rem_minutes = %s,
                        light_minutes = %s,
                        awake_minutes = %s,
                        sleep_segments = %s,
                        source = 'apple_health',
                        updated_at = CURRENT_TIMESTAMP
                    WHERE id = %s AND profile_id = %s
                    RETURNING id
                    """,
                    (
                        night["bedtime"].time(),
                        night["wake_time"].time(),
                        duration_minutes,
                        wake_count,
                        night["deep_minutes"],
                        night["rem_minutes"],
                        night["light_minutes"],
                        night["awake_minutes"],
                        json.dumps(sleep_segments),
                        existing["id"],
                        profile_id,
                    ),
                )
                row = cur.fetchone()
                updated += 1
                if row and row.get("id"):
                    affected_ids.append(str(row["id"]))
            else:
                cur.execute(
                    """
                    INSERT INTO sleep_log (
                        profile_id, date, bedtime, wake_time, duration_minutes,
                        wake_count, deep_minutes, rem_minutes, light_minutes, awake_minutes,
                        sleep_segments, source, created_at, updated_at
                    ) VALUES (
                        %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'apple_health', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
                    )
                    RETURNING id
                    """,
                    (
                        profile_id,
                        wake_date,
                        night["bedtime"].time(),
                        night["wake_time"].time(),
                        duration_minutes,
                        wake_count,
                        night["deep_minutes"],
                        night["rem_minutes"],
                        night["light_minutes"],
                        night["awake_minutes"],
                        json.dumps(sleep_segments),
                    ),
                )
                row = cur.fetchone()
                inserted += 1
                if row and row.get("id"):
                    affected_ids.append(str(row["id"]))
        except Exception as e:
            logger.warning("Sleep import row failed: %s", e)
            error_details.append({"row": row_hint, "error": str(e)})

    return {
        "rows_total": len(nights_dict),
        "inserted": inserted,
        "updated": updated,
        "skipped": skipped,
        "new_entries": inserted,
        "error_details": error_details,
        "affected_ids": affected_ids,
    }
