"""SQLite schema + connection helpers.

The DB path comes from the loaded `Config` (see `openrun.config`); pass an explicit
`Path` to `connect()` to override (handy for tests).
"""

from __future__ import annotations

import sqlite3
from pathlib import Path

from .config import default_config


SCHEMA = """
CREATE TABLE IF NOT EXISTS activities (
    activity_id        INTEGER PRIMARY KEY,
    start_time_local   TEXT,
    start_time_gmt     TEXT,
    activity_type      TEXT,
    activity_name      TEXT,
    distance_m         REAL,
    duration_s         REAL,
    moving_duration_s  REAL,
    avg_speed_mps      REAL,
    max_speed_mps      REAL,
    avg_hr             REAL,
    max_hr             REAL,
    calories           REAL,
    elevation_gain_m   REAL,
    elevation_loss_m   REAL,
    training_load      REAL,
    aerobic_te         REAL,
    anaerobic_te       REAL,
    vo2_max            REAL,
    raw                TEXT NOT NULL,
    fetched_at         TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_activities_start ON activities(start_time_local);
CREATE INDEX IF NOT EXISTS idx_activities_type  ON activities(activity_type);

CREATE TABLE IF NOT EXISTS activity_splits (
    activity_id   INTEGER NOT NULL,
    split_index   INTEGER NOT NULL,
    distance_m    REAL,
    duration_s    REAL,
    avg_hr        REAL,
    avg_speed_mps REAL,
    elevation_gain_m REAL,
    raw           TEXT NOT NULL,
    PRIMARY KEY (activity_id, split_index),
    FOREIGN KEY (activity_id) REFERENCES activities(activity_id)
);

CREATE TABLE IF NOT EXISTS daily_steps (
    calendar_date TEXT PRIMARY KEY,
    total_steps   INTEGER,
    step_goal     INTEGER,
    distance_m    REAL,
    raw           TEXT NOT NULL,
    fetched_at    TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS daily_sleep (
    calendar_date    TEXT PRIMARY KEY,
    sleep_start_gmt  TEXT,
    sleep_end_gmt    TEXT,
    deep_s           REAL,
    light_s          REAL,
    rem_s            REAL,
    awake_s          REAL,
    sleep_score      REAL,
    raw              TEXT NOT NULL,
    fetched_at       TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS daily_stress (
    calendar_date TEXT PRIMARY KEY,
    avg_stress    REAL,
    max_stress    REAL,
    raw           TEXT NOT NULL,
    fetched_at    TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS daily_hrv (
    calendar_date    TEXT PRIMARY KEY,
    weekly_avg       REAL,
    last_night_avg   REAL,
    last_night_5min  REAL,
    status           TEXT,
    raw              TEXT NOT NULL,
    fetched_at       TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS daily_body_battery (
    calendar_date TEXT PRIMARY KEY,
    charged       INTEGER,
    drained       INTEGER,
    highest       INTEGER,
    lowest        INTEGER,
    raw           TEXT NOT NULL,
    fetched_at    TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS daily_intensity_minutes (
    calendar_date    TEXT PRIMARY KEY,
    moderate_minutes INTEGER,
    vigorous_minutes INTEGER,
    raw              TEXT NOT NULL,
    fetched_at       TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS daily_resting_hr (
    calendar_date TEXT PRIMARY KEY,
    resting_hr    INTEGER,
    raw           TEXT NOT NULL,
    fetched_at    TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS activity_fit_files (
    activity_id INTEGER PRIMARY KEY,
    fit_path    TEXT NOT NULL,
    indexed_at  TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS activity_time_in_zone (
    activity_id INTEGER PRIMARY KEY,
    z1_s        REAL,
    z2_s        REAL,
    z3_s        REAL,
    z4_s        REAL,
    z5_s        REAL,
    total_s     REAL,
    source      TEXT,
    computed_at TEXT NOT NULL,
    FOREIGN KEY (activity_id) REFERENCES activities(activity_id)
);

CREATE TABLE IF NOT EXISTS sync_state (
    key        TEXT PRIMARY KEY,
    value      TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

-- Periodised training plan. One row per ISO-Monday-anchored week. The web
-- Race-plan editor reads/writes these directly; `banister_forecast` consumes
-- them to project future CTL/ATL/TSB through the race calendar.
CREATE TABLE IF NOT EXISTS race_plan (
    week_start    TEXT PRIMARY KEY,
    weekly_km     REAL,
    long_run_km   REAL,
    notes         TEXT,
    updated_at    TEXT NOT NULL
);

-- Off-watch activities the user logs by hand (strength, hikes, unrecorded runs).
-- Distinct from `activities` (Garmin-sourced) so re-ingesting from Garmin never
-- clobbers them. `external_id` is a stable string the user provides for dedupe;
-- when present, re-importing the same row upserts instead of creating duplicates.
CREATE TABLE IF NOT EXISTS manual_activities (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    activity_date TEXT NOT NULL,
    activity_type TEXT NOT NULL,
    distance_km   REAL,
    duration_min  REAL,
    training_load REAL,
    notes         TEXT,
    external_id   TEXT UNIQUE,
    source        TEXT,
    imported_at   TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_manual_date ON manual_activities(activity_date);
"""


def connect(
    db_path: Path | str | None = None,
    *,
    check_same_thread: bool = True,
) -> sqlite3.Connection:
    """Open (and create-if-missing) the SQLite DB. Defaults to config-supplied path.

    `check_same_thread=False` is needed when a single connection is reused
    across threads (e.g. Streamlit's per-rerun script threads). SQLite itself
    is thread-safe under WAL mode; the check is a Python-binding-only guard.
    """
    path = Path(db_path) if db_path is not None else default_config().db_path
    path.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(path, check_same_thread=check_same_thread)
    conn.execute("PRAGMA journal_mode = WAL")
    conn.execute("PRAGMA foreign_keys = ON")
    conn.row_factory = sqlite3.Row
    conn.executescript(SCHEMA)
    return conn


def set_state(conn: sqlite3.Connection, key: str, value: str) -> None:
    conn.execute(
        "INSERT INTO sync_state(key, value, updated_at) VALUES (?, ?, datetime('now')) "
        "ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at",
        (key, value),
    )


def get_state(conn: sqlite3.Connection, key: str) -> str | None:
    row = conn.execute("SELECT value FROM sync_state WHERE key = ?", (key,)).fetchone()
    return row["value"] if row else None
