"""SQLite-backed state store for Architect projects."""

from __future__ import annotations

import json
import sqlite3
import uuid
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Iterator, List

from .models import ArchitectProject, ProjectPhaseResult, ProjectStatus


def _utc_now() -> datetime:
    return datetime.now(timezone.utc)


def _utc_now_iso() -> str:
    return _utc_now().isoformat()


class ArchitectStateStore:
    def __init__(self, base_dir: Path | None = None) -> None:
        self.base_dir = Path(base_dir) if base_dir else (Path.home() / ".g_gremlin" / "architect")
        self.projects_dir = self.base_dir / "projects"
        self.db_path = self.base_dir / "db" / "architect.sqlite"
        self.projects_dir.mkdir(parents=True, exist_ok=True)
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    @contextmanager
    def _connect(self) -> Iterator[sqlite3.Connection]:
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def _init_db(self) -> None:
        with self._connect() as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS architect_projects (
                    project_id TEXT PRIMARY KEY,
                    blueprint_name TEXT NOT NULL,
                    status TEXT NOT NULL,
                    parameters_json TEXT NOT NULL,
                    project_dir TEXT NOT NULL,
                    phase_results_json TEXT,
                    proposal_hash TEXT,
                    sandbox_org_alias TEXT NOT NULL,
                    prod_org_alias TEXT NOT NULL,
                    variant TEXT,
                    prod_only INTEGER DEFAULT 0,
                    receipt_ids_json TEXT,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL
                )
                """
            )
            self._ensure_column(conn, "variant", "TEXT")
            self._ensure_column(conn, "prod_only", "INTEGER DEFAULT 0")

    def _ensure_column(self, conn: sqlite3.Connection, name: str, ddl: str) -> None:
        try:
            conn.execute(f"ALTER TABLE architect_projects ADD COLUMN {name} {ddl}")
        except sqlite3.OperationalError:
            return

    def create_project(
        self,
        *,
        blueprint_name: str,
        parameters: dict,
        sandbox_org_alias: str,
        prod_org_alias: str,
        variant: str | None = None,
        prod_only: bool = False,
    ) -> ArchitectProject:
        project_id = f"arch_{uuid.uuid4().hex[:12]}"
        project_dir = self.projects_dir / project_id
        self._init_project_dirs(project_dir)

        project = ArchitectProject(
            project_id=project_id,
            blueprint_name=blueprint_name,
            status=ProjectStatus.CREATED,
            parameters=parameters,
            project_dir=str(project_dir),
            sandbox_org_alias=sandbox_org_alias,
            prod_org_alias=prod_org_alias,
            phase_results={},
            receipt_ids={},
            variant=variant,
            prod_only=prod_only,
        )

        now = _utc_now_iso()
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO architect_projects (
                    project_id, blueprint_name, status, parameters_json, project_dir,
                    phase_results_json, proposal_hash, sandbox_org_alias, prod_org_alias,
                    variant, prod_only, receipt_ids_json, created_at, updated_at
                )
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    project.project_id,
                    project.blueprint_name,
                    project.status.value,
                    json.dumps(project.parameters, ensure_ascii=True),
                    project.project_dir,
                    json.dumps({}, ensure_ascii=True),
                    project.proposal_hash,
                    project.sandbox_org_alias,
                    project.prod_org_alias,
                    project.variant,
                    1 if project.prod_only else 0,
                    json.dumps(project.receipt_ids, ensure_ascii=True),
                    now,
                    now,
                ),
            )

        self._write_project_json(project)
        return project

    def get_project(self, project_id: str) -> ArchitectProject | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM architect_projects WHERE project_id = ?",
                (project_id,),
            ).fetchone()
        if not row:
            return None
        return self._row_to_project(row)

    def list_projects(self) -> List[ArchitectProject]:
        with self._connect() as conn:
            rows = conn.execute(
                "SELECT * FROM architect_projects ORDER BY created_at DESC",
            ).fetchall()
        return [self._row_to_project(row) for row in rows]

    def update_status(self, project_id: str, status: ProjectStatus) -> ArchitectProject:
        project = self.get_project(project_id)
        if not project:
            raise ValueError(f"Project not found: {project_id}")
        project.status = status
        self._persist_project(project)
        return project

    def record_phase_result(self, project_id: str, result: ProjectPhaseResult) -> ArchitectProject:
        project = self.get_project(project_id)
        if not project:
            raise ValueError(f"Project not found: {project_id}")
        project.phase_results[result.phase] = result
        self._persist_project(project)
        return project

    def update_project(self, project: ArchitectProject) -> ArchitectProject:
        self._persist_project(project)
        return project

    def _persist_project(self, project: ArchitectProject) -> None:
        now = _utc_now_iso()
        with self._connect() as conn:
            conn.execute(
                """
                UPDATE architect_projects
                SET status = ?,
                    parameters_json = ?,
                    project_dir = ?,
                    phase_results_json = ?,
                    proposal_hash = ?,
                    sandbox_org_alias = ?,
                    prod_org_alias = ?,
                    variant = ?,
                    prod_only = ?,
                    receipt_ids_json = ?,
                    updated_at = ?
                WHERE project_id = ?
                """,
                (
                    project.status.value,
                    json.dumps(project.parameters, ensure_ascii=True),
                    project.project_dir,
                    json.dumps(
                        {k: v.model_dump() for k, v in project.phase_results.items()},
                        ensure_ascii=True,
                    ),
                    project.proposal_hash,
                    project.sandbox_org_alias,
                    project.prod_org_alias,
                    project.variant,
                    1 if project.prod_only else 0,
                    json.dumps(project.receipt_ids, ensure_ascii=True),
                    now,
                    project.project_id,
                ),
            )
        self._write_project_json(project)

    def _row_to_project(self, row: sqlite3.Row) -> ArchitectProject:
        phase_results_raw = json.loads(row["phase_results_json"] or "{}")
        phase_results = {
            key: ProjectPhaseResult.model_validate(value)
            for key, value in phase_results_raw.items()
        }
        receipt_ids = json.loads(row["receipt_ids_json"] or "{}")
        status_value = row["status"]
        if status_value == "promoting_prod":
            status_value = ProjectStatus.DEPLOYING_PROD.value
        elif status_value == "promoted_prod":
            status_value = ProjectStatus.DEPLOYED_PROD.value
        return ArchitectProject(
            project_id=row["project_id"],
            blueprint_name=row["blueprint_name"],
            status=ProjectStatus(status_value),
            parameters=json.loads(row["parameters_json"] or "{}"),
            project_dir=row["project_dir"],
            phase_results=phase_results,
            proposal_hash=row["proposal_hash"],
            sandbox_org_alias=row["sandbox_org_alias"],
            prod_org_alias=row["prod_org_alias"],
            receipt_ids=receipt_ids,
            variant=row["variant"],
            prod_only=bool(row["prod_only"] or 0),
        )

    def _init_project_dirs(self, project_dir: Path) -> None:
        project_dir.mkdir(parents=True, exist_ok=True)
        (project_dir / "discovery" / "raw").mkdir(parents=True, exist_ok=True)
        (project_dir / "proposal" / "metadata_pack").mkdir(parents=True, exist_ok=True)
        (project_dir / "proposal" / "hubspot_pack").mkdir(parents=True, exist_ok=True)
        (project_dir / "verify_sandbox").mkdir(parents=True, exist_ok=True)
        (project_dir / "deploy_prod").mkdir(parents=True, exist_ok=True)
        (project_dir / "promote_prod").mkdir(parents=True, exist_ok=True)

    def _write_project_json(self, project: ArchitectProject) -> None:
        path = Path(project.project_dir) / "project.json"
        payload = project.model_dump()
        path.write_text(json.dumps(payload, indent=2, ensure_ascii=True), encoding="utf-8")


__all__ = ["ArchitectStateStore"]
