"""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 g_gremlin.paths import gremlin_path

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 gremlin_path("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"]
