"""Space DB CRUD operations.

Provides database operations for spaces and space_paths tables.
"""

from __future__ import annotations

import sqlite3
import uuid
from datetime import datetime, timezone
from typing import Any

_ALLOWED_SPACE_UPDATE_COLUMNS: set[str] = {
    "file_hash",
    "last_loaded_at",
    "updated_at",
}


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _uuid() -> str:
    return str(uuid.uuid4())


# --- Spaces ---


def create_space(
    db: sqlite3.Connection,
    name: str,
    file_path: str,
    file_hash: str,
) -> dict[str, Any]:
    sid = _uuid()
    now = _now()
    db.execute(
        "INSERT INTO spaces (id, name, file_path, file_hash, last_loaded_at, created_at, updated_at)"
        " VALUES (?, ?, ?, ?, ?, ?, ?)",
        (sid, name, file_path, file_hash, now, now, now),
    )
    db.commit()
    return {
        "id": sid,
        "name": name,
        "file_path": file_path,
        "file_hash": file_hash,
        "last_loaded_at": now,
        "created_at": now,
        "updated_at": now,
    }


def get_space(db: sqlite3.Connection, space_id: str) -> dict[str, Any] | None:
    row = db.execute_fetchone(
        "SELECT id, name, file_path, file_hash, last_loaded_at, created_at, updated_at FROM spaces WHERE id = ?",
        (space_id,),
    )
    if not row:
        return None
    return dict(row)


def get_space_by_name(db: sqlite3.Connection, name: str) -> dict[str, Any] | None:
    row = db.execute_fetchone(
        "SELECT id, name, file_path, file_hash, last_loaded_at, created_at, updated_at"
        " FROM spaces WHERE LOWER(name) = LOWER(?)",
        (name,),
    )
    if not row:
        return None
    return dict(row)


def list_spaces(db: sqlite3.Connection) -> list[dict[str, Any]]:
    rows = db.execute_fetchall(
        "SELECT id, name, file_path, file_hash, last_loaded_at, created_at, updated_at FROM spaces ORDER BY name"
    )
    return [dict(r) for r in rows]


def update_space(
    db: sqlite3.Connection,
    space_id: str,
    **kwargs: Any,
) -> dict[str, Any] | None:
    space = get_space(db, space_id)
    if not space:
        return None

    cols: dict[str, Any] = {"updated_at": _now()}
    for key, val in kwargs.items():
        if key not in _ALLOWED_SPACE_UPDATE_COLUMNS:
            raise ValueError(f"Column {key!r} not in allowed update columns")
        cols[key] = val

    parts: list[str] = []
    params: list[Any] = []
    for col, val in cols.items():
        parts.append(f"{col} = ?")
        params.append(val)
    set_clause = ", ".join(parts)
    params.append(space_id)
    db.execute(f"UPDATE spaces SET {set_clause} WHERE id = ?", tuple(params))
    db.commit()
    return get_space(db, space_id)


def delete_space(db: sqlite3.Connection, space_id: str) -> bool:
    space = get_space(db, space_id)
    if not space:
        return False
    # Clear orphaned space_id references (no FK constraint on ALTER TABLE columns)
    db.execute("UPDATE conversations SET space_id = NULL WHERE space_id = ?", (space_id,))
    db.execute("UPDATE folders SET space_id = NULL WHERE space_id = ?", (space_id,))
    db.execute("DELETE FROM spaces WHERE id = ?", (space_id,))
    db.commit()
    return True


# --- Space paths ---


def sync_space_paths(
    db: sqlite3.Connection,
    space_id: str,
    paths: list[dict[str, str]],
) -> None:
    """Replace all paths for a space with the given list."""
    db.execute("DELETE FROM space_paths WHERE space_id = ?", (space_id,))
    now = _now()
    for p in paths:
        pid = _uuid()
        db.execute(
            "INSERT INTO space_paths (id, space_id, repo_url, local_path, created_at) VALUES (?, ?, ?, ?, ?)",
            (pid, space_id, p["repo_url"], p["local_path"], now),
        )
    db.commit()


def get_space_paths(db: sqlite3.Connection, space_id: str) -> list[dict[str, Any]]:
    rows = db.execute_fetchall(
        "SELECT id, space_id, repo_url, local_path, created_at FROM space_paths WHERE space_id = ? ORDER BY created_at",
        (space_id,),
    )
    return [dict(r) for r in rows]


# --- Conversation association ---


def update_conversation_space(
    db: sqlite3.Connection,
    conversation_id: str,
    space_id: str | None,
) -> None:
    now = _now()
    db.execute(
        "UPDATE conversations SET space_id = ?, updated_at = ? WHERE id = ?",
        (space_id, now, conversation_id),
    )
    db.commit()


def count_space_conversations(db: sqlite3.Connection, space_id: str) -> int:
    row = db.execute_fetchone(
        "SELECT COUNT(*) AS cnt FROM conversations WHERE space_id = ?",
        (space_id,),
    )
    return row["cnt"] if row else 0


# --- Resolve by cwd ---


def resolve_space_by_cwd(db: sqlite3.Connection, cwd: str) -> dict[str, Any] | None:
    """Find a space whose local_path is a prefix of the given cwd."""
    rows = db.execute_fetchall(
        "SELECT sp.space_id, sp.local_path FROM space_paths sp ORDER BY LENGTH(sp.local_path) DESC"
    )
    for row in rows:
        local_path = row["local_path"]
        if cwd == local_path or cwd.startswith(local_path + "/"):
            return get_space(db, row["space_id"])
    return None
