"""目録の登録（症例・撮影・ファイル・所見・由来）と，登録のための照会.

所見（`annotations`）は INSERT だけを行い，UPDATE・DELETE しない（スキーマのトリガでも拒否される）．
"""

import sqlite3
from datetime import UTC, datetime

from pydantic import BaseModel, ConfigDict

SUBJECT_PREFIX = "S"
CAPTURE_PREFIX = "C"
# 研究用 ID・撮影 ID は接頭辞 + 6 桁（SPEC_02「ID の書式」．スキーマの CHECK と同じ）
ID_DIGITS = 6
_MAX_ID_NUMBER = 10**ID_DIGITS - 1


class AssetRecord(BaseModel):
    """目録の `assets` の 1 行．"""

    model_config = ConfigDict(frozen=True)

    sha256: str
    kind: str
    capture_id: str | None
    ext: str
    size_bytes: int
    source: str
    source_name: str


class CaptureRecord(BaseModel):
    """目録の `captures` の 1 行．"""

    model_config = ConfigDict(frozen=True)

    capture_id: str
    subject_id: str
    captured_on: str | None
    device: str | None


def utc_timestamp(now: datetime | None = None) -> str:
    """目録に保存する日時の文字列（UTC の `YYYY-MM-DDTHH:MM:SSZ`．秒まで）を返す．

    Args:
        now: 日時（省略時は現在．タイムゾーンなしは UTC とみなす）．

    Returns:
        `2026-10-02T09:00:00Z` の形の文字列．
    """
    moment = now if now is not None else datetime.now(UTC)
    if moment.tzinfo is not None:
        moment = moment.astimezone(UTC)
    # スキーマの CHECK が小数秒を拒否するため秒までにする
    return moment.strftime("%Y-%m-%dT%H:%M:%SZ")


def format_id(prefix: str, number: int) -> str:
    """研究用 ID・撮影 ID の文字列を作る（例: `S000123`）．

    Args:
        prefix: `S` または `C`．
        number: 1 以上の番号．

    Returns:
        接頭辞 + 6 桁の ID．

    Raises:
        ValueError: 番号が 6 桁に収まらない．
    """
    if not 1 <= number <= _MAX_ID_NUMBER:
        raise ValueError(f"ID の番号が {ID_DIGITS} 桁に収まらない（{prefix}）")
    return f"{prefix}{number:0{ID_DIGITS}d}"


def parse_id_number(value: str | None) -> int:
    """目録・対応表にある研究用 ID・撮影 ID の番号を返す（`format_id` の逆．None なら 0）．"""
    # ID の書式はスキーマの CHECK で保証されているので，接頭辞を除けば整数になる
    return int(value[1:]) if value else 0


def max_subject_number(conn: sqlite3.Connection) -> int:
    """目録の症例の研究用 ID の最大の番号を返す（無ければ 0）．"""
    (value,) = conn.execute("SELECT MAX(subject_id) FROM subjects").fetchone()
    return parse_id_number(value)


def max_capture_number(conn: sqlite3.Connection) -> int:
    """目録の撮影 ID の最大の番号を返す（無ければ 0）．"""
    (value,) = conn.execute("SELECT MAX(capture_id) FROM captures").fetchone()
    return parse_id_number(value)


def subject_exists(conn: sqlite3.Connection, subject_id: str) -> bool:
    """症例が目録にあるか．"""
    row = conn.execute("SELECT 1 FROM subjects WHERE subject_id = ?", (subject_id,)).fetchone()
    return row is not None


def get_asset(conn: sqlite3.Connection, sha256: str) -> AssetRecord | None:
    """ファイルの行を SHA-256 で引く（無ければ None）．"""
    row = conn.execute(
        "SELECT sha256, kind, capture_id, ext, size_bytes, source, source_name"
        " FROM assets WHERE sha256 = ?",
        (sha256,),
    ).fetchone()
    if row is None:
        return None
    return AssetRecord(
        sha256=row[0],
        kind=row[1],
        capture_id=row[2],
        ext=row[3],
        size_bytes=row[4],
        source=row[5],
        source_name=row[6],
    )


def find_asset_sha256_by_source_name(
    conn: sqlite3.Connection, source: str, source_name: str
) -> list[str]:
    """取り込み元の名前と相対パスから，登録済みのファイルの SHA-256 を引く．"""
    rows = conn.execute(
        "SELECT sha256 FROM assets WHERE source = ? AND source_name = ? ORDER BY sha256",
        (source, source_name),
    )
    return [sha256 for (sha256,) in rows]


def get_capture(conn: sqlite3.Connection, capture_id: str) -> CaptureRecord | None:
    """撮影の行を引く（無ければ None）．"""
    row = conn.execute(
        "SELECT capture_id, subject_id, captured_on, device FROM captures WHERE capture_id = ?",
        (capture_id,),
    ).fetchone()
    if row is None:
        return None
    return CaptureRecord(capture_id=row[0], subject_id=row[1], captured_on=row[2], device=row[3])


def capture_annotation_values(
    conn: sqlite3.Connection, capture_id: str, kind: str, source: str
) -> list[str]:
    """撮影に付いた所見のうち，種類と由来が一致するものの値（JSON の文字列）を返す．

    訂正された行（`superseded_by` あり）も含める（取り込み元から読んだ元の値と照合するため）．
    """
    rows = conn.execute(
        "SELECT value FROM annotations"
        " WHERE capture_id = ? AND kind = ? AND source = ? ORDER BY annotation_id",
        (capture_id, kind, source),
    )
    return [value for (value,) in rows]


def insert_subject(conn: sqlite3.Connection, subject_id: str, created_at: str) -> None:
    """症例を登録する．"""
    conn.execute(
        "INSERT INTO subjects (subject_id, created_at) VALUES (?, ?)", (subject_id, created_at)
    )


def insert_capture(
    conn: sqlite3.Connection,
    capture_id: str,
    subject_id: str,
    *,
    captured_on: str | None,
    device: str | None,
) -> None:
    """撮影を登録する．"""
    conn.execute(
        "INSERT INTO captures (capture_id, subject_id, captured_on, device) VALUES (?, ?, ?, ?)",
        (capture_id, subject_id, captured_on, device),
    )


def insert_asset(conn: sqlite3.Connection, asset: AssetRecord, registered_at: str) -> None:
    """ファイルを登録する．"""
    conn.execute(
        "INSERT INTO assets"
        " (sha256, kind, capture_id, ext, size_bytes, source, source_name, registered_at)"
        " VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
        (
            asset.sha256,
            asset.kind,
            asset.capture_id,
            asset.ext,
            asset.size_bytes,
            asset.source,
            asset.source_name,
            registered_at,
        ),
    )


def insert_capture_annotation(
    conn: sqlite3.Connection,
    *,
    kind: str,
    capture_id: str,
    value_json: str,
    source: str,
    created_at: str,
    annotator: str | None = None,
) -> int:
    """撮影に付く所見を追加する（値は `kinds` の型で検証済みの JSON の文字列）．

    Returns:
        追加した行の `annotation_id`．
    """
    cursor = conn.execute(
        "INSERT INTO annotations (kind, capture_id, value, annotator, source, created_at)"
        " VALUES (?, ?, ?, ?, ?, ?)",
        (kind, capture_id, value_json, annotator, source, created_at),
    )
    return int(cursor.lastrowid)


def insert_derivation(
    conn: sqlite3.Connection,
    *,
    tool: str,
    tool_version: str,
    mode: str | None,
    note: str | None,
    outputs: list[tuple[str, str]],
    inputs: list[tuple[str, str]] | None = None,
    performed_at: str | None = None,
) -> int:
    """由来（プログラム・版）と，その入力・出力のファイルを登録する．

    Args:
        conn: 目録への接続．
        tool: プログラム名．
        tool_version: プログラムの版（不明なら `unknown`）．
        mode: 実行モード・設定．
        note: 備考．
        outputs: 出力ファイル（SHA-256, 役割）．
        inputs: 入力ファイル（SHA-256, 役割）．省略時は入力なし．
        performed_at: 実行日時（不明なら None）．

    Returns:
        追加した行の `derivation_id`．
    """
    cursor = conn.execute(
        "INSERT INTO derivations (tool, tool_version, mode, performed_at, note)"
        " VALUES (?, ?, ?, ?, ?)",
        (tool, tool_version, mode, performed_at, note),
    )
    derivation_id = int(cursor.lastrowid)
    conn.executemany(
        "INSERT INTO derivation_inputs (derivation_id, sha256, role) VALUES (?, ?, ?)",
        [(derivation_id, sha256, role) for sha256, role in inputs or []],
    )
    conn.executemany(
        "INSERT INTO derivation_outputs (derivation_id, sha256, role) VALUES (?, ?, ?)",
        [(derivation_id, sha256, role) for sha256, role in outputs],
    )
    return derivation_id
