"""Prompt registry, promotion, experiments, and recommendations."""
from __future__ import annotations

from dataclasses import asdict, dataclass, field
from datetime import UTC, datetime
import hashlib
import json
import os
from pathlib import Path
import sqlite3
from typing import Any
from uuid import uuid4

from logos_adk.persistence import (
    get_postgresql_schema_version,
    get_sqlite_schema_version,
    set_postgresql_schema_version,
    set_sqlite_schema_version,
)
from logos_adk.postgres_utils import import_psycopg, resolve_database_url


PROMPT_REGISTRY_SCHEMA_KIND = "prompt_registry"
PROMPT_REGISTRY_SCHEMA_VERSION = 1


def _utcnow() -> str:
    return datetime.now(UTC).isoformat()


@dataclass
class PromptDefinition:
    """Prompt registry entry."""

    name: str
    description: str | None = None
    created_at: str = field(default_factory=_utcnow)


@dataclass
class PromptVersionRecord:
    """A single version of a named prompt."""

    id: str
    prompt_name: str
    version: int
    content: str
    tags: list[str] = field(default_factory=list)
    notes: str | None = None
    created_at: str = field(default_factory=_utcnow)
    created_by: str | None = None


@dataclass
class PromptDeployment:
    """An active prompt deployment for a segment."""

    id: str
    prompt_name: str
    version: int
    environment: str = "prod"
    workspace_id: str | None = None
    task_type: str | None = None
    user_segment: str | None = None
    active: bool = True
    promoted_at: str = field(default_factory=_utcnow)
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class PromptExperiment:
    """Live A/B prompt experiment."""

    id: str
    prompt_name: str
    baseline_version: int
    challenger_versions: list[int]
    metric: str = "reward"
    rollout_percentage: int = 50
    environment: str = "prod"
    workspace_id: str | None = None
    task_type: str | None = None
    user_segment: str | None = None
    status: str = "active"
    created_at: str = field(default_factory=_utcnow)
    ended_at: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class PromptRecommendation:
    """Stored prompt improvement recommendation."""

    id: str
    prompt_name: str
    kind: str
    title: str
    summary: str
    proposed_patch: str | None = None
    based_on_version: int | None = None
    failure_counts: dict[str, int] = field(default_factory=dict)
    status: str = "open"
    created_at: str = field(default_factory=_utcnow)
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class PromptResolution:
    """Resolved prompt version for a concrete run."""

    prompt_name: str
    version: int
    content: str
    tags: list[str] = field(default_factory=list)
    experiment_id: str | None = None
    variant: str | None = None
    workspace_id: str | None = None
    task_type: str | None = None
    user_segment: str | None = None


class PromptRegistry:
    """Persistent prompt registry with SQLite and PostgreSQL support."""

    def __init__(self, path: str = ".logos_adk/prompts.db", dsn: str | None = None) -> None:
        self.path = path
        self.dsn = resolve_database_url(
            dsn or os.getenv("LOGOS_ADK_PROMPT_REGISTRY_DSN"),
            env_vars=("LOGOS_ADK_PROMPT_REGISTRY_DSN", "LOGOS_ADK_DATABASE_URL", "DATABASE_URL"),
        )

    def _connect(self) -> sqlite3.Connection | Any:
        if self.dsn:
            connection = import_psycopg().connect(self.dsn)
            self._ensure_schema(connection)
            return connection
        db_path = Path(self.path)
        if db_path.parent and not db_path.parent.exists():
            db_path.parent.mkdir(parents=True, exist_ok=True)
        connection = sqlite3.connect(self.path)
        connection.row_factory = sqlite3.Row
        return connection

    def _ensure_schema(self, connection: sqlite3.Connection | Any) -> None:
        if self.dsn:
            recorded_version = get_postgresql_schema_version(connection, PROMPT_REGISTRY_SCHEMA_KIND)
            with connection.cursor() as cursor:
                cursor.execute(
                    """
                    CREATE TABLE IF NOT EXISTS prompts (
                        name TEXT PRIMARY KEY,
                        description TEXT,
                        created_at TEXT NOT NULL
                    )
                    """
                )
                cursor.execute(
                    """
                    CREATE TABLE IF NOT EXISTS prompt_versions (
                        id TEXT PRIMARY KEY,
                        prompt_name TEXT NOT NULL,
                        version INTEGER NOT NULL,
                        content TEXT NOT NULL,
                        tags_json JSONB,
                        notes TEXT,
                        created_at TEXT NOT NULL,
                        created_by TEXT,
                        UNIQUE (prompt_name, version)
                    )
                    """
                )
                cursor.execute(
                    """
                    CREATE TABLE IF NOT EXISTS prompt_deployments (
                        id TEXT PRIMARY KEY,
                        prompt_name TEXT NOT NULL,
                        version INTEGER NOT NULL,
                        environment TEXT NOT NULL,
                        workspace_id TEXT,
                        task_type TEXT,
                        user_segment TEXT,
                        active INTEGER NOT NULL DEFAULT 1,
                        promoted_at TEXT NOT NULL,
                        metadata_json JSONB
                    )
                    """
                )
                cursor.execute(
                    "CREATE INDEX IF NOT EXISTS idx_prompt_deployments_lookup "
                    "ON prompt_deployments (prompt_name, environment, active, workspace_id, task_type, user_segment)"
                )
                cursor.execute(
                    """
                    CREATE TABLE IF NOT EXISTS prompt_experiments (
                        id TEXT PRIMARY KEY,
                        prompt_name TEXT NOT NULL,
                        baseline_version INTEGER NOT NULL,
                        challenger_versions_json JSONB NOT NULL,
                        metric TEXT NOT NULL,
                        rollout_percentage INTEGER NOT NULL,
                        environment TEXT NOT NULL,
                        workspace_id TEXT,
                        task_type TEXT,
                        user_segment TEXT,
                        status TEXT NOT NULL,
                        created_at TEXT NOT NULL,
                        ended_at TEXT,
                        metadata_json JSONB
                    )
                    """
                )
                cursor.execute(
                    "CREATE INDEX IF NOT EXISTS idx_prompt_experiments_lookup "
                    "ON prompt_experiments (prompt_name, status, environment, workspace_id, task_type, user_segment)"
                )
                cursor.execute(
                    """
                    CREATE TABLE IF NOT EXISTS prompt_recommendations (
                        id TEXT PRIMARY KEY,
                        prompt_name TEXT NOT NULL,
                        kind TEXT NOT NULL,
                        title TEXT NOT NULL,
                        summary TEXT NOT NULL,
                        proposed_patch TEXT,
                        based_on_version INTEGER,
                        failure_counts_json JSONB,
                        status TEXT NOT NULL,
                        created_at TEXT NOT NULL,
                        metadata_json JSONB
                    )
                    """
                )
                cursor.execute(
                    "CREATE INDEX IF NOT EXISTS idx_prompt_recommendations_prompt_created "
                    "ON prompt_recommendations (prompt_name, created_at DESC)"
                )
            if recorded_version != PROMPT_REGISTRY_SCHEMA_VERSION:
                set_postgresql_schema_version(connection, PROMPT_REGISTRY_SCHEMA_KIND, PROMPT_REGISTRY_SCHEMA_VERSION)
            connection.commit()
            return

        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS prompts (
                name TEXT PRIMARY KEY,
                description TEXT,
                created_at TEXT NOT NULL
            )
            """
        )
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS prompt_versions (
                id TEXT PRIMARY KEY,
                prompt_name TEXT NOT NULL,
                version INTEGER NOT NULL,
                content TEXT NOT NULL,
                tags_json TEXT,
                notes TEXT,
                created_at TEXT NOT NULL,
                created_by TEXT,
                UNIQUE (prompt_name, version)
            )
            """
        )
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS prompt_deployments (
                id TEXT PRIMARY KEY,
                prompt_name TEXT NOT NULL,
                version INTEGER NOT NULL,
                environment TEXT NOT NULL,
                workspace_id TEXT,
                task_type TEXT,
                user_segment TEXT,
                active INTEGER NOT NULL DEFAULT 1,
                promoted_at TEXT NOT NULL,
                metadata_json TEXT
            )
            """
        )
        connection.execute(
            "CREATE INDEX IF NOT EXISTS idx_prompt_deployments_lookup "
            "ON prompt_deployments (prompt_name, environment, active, workspace_id, task_type, user_segment)"
        )
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS prompt_experiments (
                id TEXT PRIMARY KEY,
                prompt_name TEXT NOT NULL,
                baseline_version INTEGER NOT NULL,
                challenger_versions_json TEXT NOT NULL,
                metric TEXT NOT NULL,
                rollout_percentage INTEGER NOT NULL,
                environment TEXT NOT NULL,
                workspace_id TEXT,
                task_type TEXT,
                user_segment TEXT,
                status TEXT NOT NULL,
                created_at TEXT NOT NULL,
                ended_at TEXT,
                metadata_json TEXT
            )
            """
        )
        connection.execute(
            "CREATE INDEX IF NOT EXISTS idx_prompt_experiments_lookup "
            "ON prompt_experiments (prompt_name, status, environment, workspace_id, task_type, user_segment)"
        )
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS prompt_recommendations (
                id TEXT PRIMARY KEY,
                prompt_name TEXT NOT NULL,
                kind TEXT NOT NULL,
                title TEXT NOT NULL,
                summary TEXT NOT NULL,
                proposed_patch TEXT,
                based_on_version INTEGER,
                failure_counts_json TEXT,
                status TEXT NOT NULL,
                created_at TEXT NOT NULL,
                metadata_json TEXT
            )
            """
        )
        connection.execute(
            "CREATE INDEX IF NOT EXISTS idx_prompt_recommendations_prompt_created "
            "ON prompt_recommendations (prompt_name, created_at DESC)"
        )
        recorded_version = get_sqlite_schema_version(connection, PROMPT_REGISTRY_SCHEMA_KIND)
        if recorded_version != PROMPT_REGISTRY_SCHEMA_VERSION:
            set_sqlite_schema_version(connection, PROMPT_REGISTRY_SCHEMA_KIND, PROMPT_REGISTRY_SCHEMA_VERSION)
        connection.commit()

    def _placeholder(self) -> str:
        return "%s" if self.dsn else "?"

    def _json_placeholder(self) -> str:
        return "%s::jsonb" if self.dsn else "?"

    def ensure_prompt(self, name: str, description: str | None = None) -> PromptDefinition:
        placeholder = self._placeholder()
        with self._connect() as connection:
            self._ensure_schema(connection)
            query = (
                "INSERT INTO prompts (name, description, created_at) "
                f"VALUES ({placeholder}, {placeholder}, {placeholder}) "
                "ON CONFLICT(name) DO UPDATE SET "
                "description = COALESCE(excluded.description, prompts.description)"
            )
            _execute(connection, query, (name, description, _utcnow()), self.dsn)
            connection.commit()
            row = _fetchone(
                connection,
                f"SELECT * FROM prompts WHERE name = {placeholder}",
                (name,),
                self.dsn,
            )
        return self._row_to_prompt(row)

    def create_version(
        self,
        prompt_name: str,
        *,
        content: str,
        tags: list[str] | None = None,
        notes: str | None = None,
        created_by: str | None = None,
    ) -> PromptVersionRecord:
        placeholder = self._placeholder()
        json_placeholder = self._json_placeholder()
        with self._connect() as connection:
            self._ensure_schema(connection)
            self.ensure_prompt(prompt_name)
            current = _fetchone(
                connection,
                f"SELECT COALESCE(MAX(version), 0) AS max_version FROM prompt_versions WHERE prompt_name = {placeholder}",
                (prompt_name,),
                self.dsn,
            )
            next_version = int(_row_value(current, "max_version", 0) if current else 0) + 1
            record = PromptVersionRecord(
                id=uuid4().hex,
                prompt_name=prompt_name,
                version=next_version,
                content=content,
                tags=list(tags or []),
                notes=notes,
                created_by=created_by,
            )
            _execute(
                connection,
                "INSERT INTO prompt_versions "
                "(id, prompt_name, version, content, tags_json, notes, created_at, created_by) "
                f"VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {json_placeholder}, {placeholder}, {placeholder}, {placeholder})",
                (
                    record.id,
                    record.prompt_name,
                    record.version,
                    record.content,
                    json.dumps(record.tags),
                    record.notes,
                    record.created_at,
                    record.created_by,
                ),
                self.dsn,
            )
            connection.commit()
        return record

    def get_version(self, prompt_name: str, version: int) -> PromptVersionRecord | None:
        placeholder = self._placeholder()
        with self._connect() as connection:
            self._ensure_schema(connection)
            row = _fetchone(
                connection,
                f"SELECT * FROM prompt_versions WHERE prompt_name = {placeholder} AND version = {placeholder}",
                (prompt_name, version),
                self.dsn,
            )
        return self._row_to_version(row) if row else None

    def list_versions(self, prompt_name: str) -> list[PromptVersionRecord]:
        placeholder = self._placeholder()
        with self._connect() as connection:
            self._ensure_schema(connection)
            rows = _fetchall(
                connection,
                f"SELECT * FROM prompt_versions WHERE prompt_name = {placeholder} ORDER BY version DESC",
                (prompt_name,),
                self.dsn,
            )
        return [self._row_to_version(row) for row in rows]

    def promote(
        self,
        prompt_name: str,
        *,
        version: int,
        environment: str = "prod",
        workspace_id: str | None = None,
        task_type: str | None = None,
        user_segment: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> PromptDeployment:
        placeholder = self._placeholder()
        json_placeholder = self._json_placeholder()
        if self.get_version(prompt_name, version) is None:
            raise ValueError(f"Prompt version not found: {prompt_name}@{version}")
        with self._connect() as connection:
            self._ensure_schema(connection)
            _execute(
                connection,
                "UPDATE prompt_deployments SET active = 0 "
                f"WHERE prompt_name = {placeholder} "
                f"AND environment = {placeholder} "
                f"AND COALESCE(workspace_id, '') = COALESCE({placeholder}, '') "
                f"AND COALESCE(task_type, '') = COALESCE({placeholder}, '') "
                f"AND COALESCE(user_segment, '') = COALESCE({placeholder}, '')",
                (prompt_name, environment, workspace_id, task_type, user_segment),
                self.dsn,
            )
            record = PromptDeployment(
                id=uuid4().hex,
                prompt_name=prompt_name,
                version=version,
                environment=environment,
                workspace_id=workspace_id,
                task_type=task_type,
                user_segment=user_segment,
                metadata=dict(metadata or {}),
            )
            _execute(
                connection,
                "INSERT INTO prompt_deployments "
                "(id, prompt_name, version, environment, workspace_id, task_type, user_segment, active, promoted_at, metadata_json) "
                f"VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {json_placeholder})",
                (
                    record.id,
                    record.prompt_name,
                    record.version,
                    record.environment,
                    record.workspace_id,
                    record.task_type,
                    record.user_segment,
                    1 if record.active else 0,
                    record.promoted_at,
                    json.dumps(record.metadata),
                ),
                self.dsn,
            )
            connection.commit()
        return record

    def rollback(
        self,
        prompt_name: str,
        *,
        environment: str = "prod",
        workspace_id: str | None = None,
        task_type: str | None = None,
        user_segment: str | None = None,
    ) -> PromptDeployment:
        deployments = self.list_deployments(
            prompt_name,
            environment=environment,
            workspace_id=workspace_id,
            task_type=task_type,
            user_segment=user_segment,
            include_inactive=True,
        )
        if len(deployments) < 2:
            raise ValueError("No previous deployment available for rollback")
        previous = deployments[1]
        return self.promote(
            prompt_name,
            version=previous.version,
            environment=environment,
            workspace_id=workspace_id,
            task_type=task_type,
            user_segment=user_segment,
            metadata={"rollback_from_version": deployments[0].version},
        )

    def list_deployments(
        self,
        prompt_name: str,
        *,
        environment: str = "prod",
        workspace_id: str | None = None,
        task_type: str | None = None,
        user_segment: str | None = None,
        include_inactive: bool = False,
    ) -> list[PromptDeployment]:
        placeholder = self._placeholder()
        clauses = [f"prompt_name = {placeholder}", f"environment = {placeholder}"]
        params: list[Any] = [prompt_name, environment]
        if not include_inactive:
            clauses.append("active = 1")
        if workspace_id is not None:
            clauses.append(f"workspace_id = {placeholder}")
            params.append(workspace_id)
        if task_type is not None:
            clauses.append(f"task_type = {placeholder}")
            params.append(task_type)
        if user_segment is not None:
            clauses.append(f"user_segment = {placeholder}")
            params.append(user_segment)
        with self._connect() as connection:
            self._ensure_schema(connection)
            rows = _fetchall(
                connection,
                f"SELECT * FROM prompt_deployments WHERE {' AND '.join(clauses)} ORDER BY promoted_at DESC",
                params,
                self.dsn,
            )
        return [self._row_to_deployment(row) for row in rows]

    def create_experiment(
        self,
        prompt_name: str,
        *,
        baseline_version: int,
        challenger_versions: list[int],
        rollout_percentage: int = 50,
        metric: str = "reward",
        environment: str = "prod",
        workspace_id: str | None = None,
        task_type: str | None = None,
        user_segment: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> PromptExperiment:
        placeholder = self._placeholder()
        json_placeholder = self._json_placeholder()
        if not challenger_versions:
            raise ValueError("challenger_versions must not be empty")
        if self.get_version(prompt_name, baseline_version) is None:
            raise ValueError(f"Baseline version not found: {prompt_name}@{baseline_version}")
        for version in challenger_versions:
            if self.get_version(prompt_name, version) is None:
                raise ValueError(f"Challenger version not found: {prompt_name}@{version}")
        record = PromptExperiment(
            id=uuid4().hex,
            prompt_name=prompt_name,
            baseline_version=baseline_version,
            challenger_versions=list(challenger_versions),
            rollout_percentage=max(1, min(100, rollout_percentage)),
            metric=metric,
            environment=environment,
            workspace_id=workspace_id,
            task_type=task_type,
            user_segment=user_segment,
            metadata=dict(metadata or {}),
        )
        with self._connect() as connection:
            self._ensure_schema(connection)
            _execute(
                connection,
                "INSERT INTO prompt_experiments "
                "(id, prompt_name, baseline_version, challenger_versions_json, metric, rollout_percentage, "
                " environment, workspace_id, task_type, user_segment, status, created_at, ended_at, metadata_json) "
                f"VALUES ({placeholder}, {placeholder}, {placeholder}, {json_placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {json_placeholder})",
                (
                    record.id,
                    record.prompt_name,
                    record.baseline_version,
                    json.dumps(record.challenger_versions),
                    record.metric,
                    record.rollout_percentage,
                    record.environment,
                    record.workspace_id,
                    record.task_type,
                    record.user_segment,
                    record.status,
                    record.created_at,
                    record.ended_at,
                    json.dumps(record.metadata),
                ),
                self.dsn,
            )
            connection.commit()
        return record

    def get_experiment(self, experiment_id: str) -> PromptExperiment | None:
        placeholder = self._placeholder()
        with self._connect() as connection:
            self._ensure_schema(connection)
            row = _fetchone(
                connection,
                f"SELECT * FROM prompt_experiments WHERE id = {placeholder}",
                (experiment_id,),
                self.dsn,
            )
        return self._row_to_experiment(row) if row else None

    def list_experiments(self, prompt_name: str | None = None, *, status: str | None = None) -> list[PromptExperiment]:
        placeholder = self._placeholder()
        clauses: list[str] = []
        params: list[Any] = []
        if prompt_name is not None:
            clauses.append(f"prompt_name = {placeholder}")
            params.append(prompt_name)
        if status is not None:
            clauses.append(f"status = {placeholder}")
            params.append(status)
        where_sql = f"WHERE {' AND '.join(clauses)}" if clauses else ""
        with self._connect() as connection:
            self._ensure_schema(connection)
            rows = _fetchall(
                connection,
                f"SELECT * FROM prompt_experiments {where_sql} ORDER BY created_at DESC",
                params,
                self.dsn,
            )
        return [self._row_to_experiment(row) for row in rows]

    def end_experiment(self, experiment_id: str, *, status: str = "completed") -> PromptExperiment:
        placeholder = self._placeholder()
        with self._connect() as connection:
            self._ensure_schema(connection)
            _execute(
                connection,
                f"UPDATE prompt_experiments SET status = {placeholder}, ended_at = {placeholder} WHERE id = {placeholder}",
                (status, _utcnow(), experiment_id),
                self.dsn,
            )
            connection.commit()
        record = self.get_experiment(experiment_id)
        if record is None:
            raise ValueError(f"Experiment not found: {experiment_id}")
        return record

    def save_recommendation(self, record: PromptRecommendation) -> PromptRecommendation:
        placeholder = self._placeholder()
        json_placeholder = self._json_placeholder()
        with self._connect() as connection:
            self._ensure_schema(connection)
            _execute(
                connection,
                "INSERT INTO prompt_recommendations "
                "(id, prompt_name, kind, title, summary, proposed_patch, based_on_version, "
                " failure_counts_json, status, created_at, metadata_json) "
                f"VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {json_placeholder}, {placeholder}, {placeholder}, {json_placeholder})",
                (
                    record.id,
                    record.prompt_name,
                    record.kind,
                    record.title,
                    record.summary,
                    record.proposed_patch,
                    record.based_on_version,
                    json.dumps(record.failure_counts),
                    record.status,
                    record.created_at,
                    json.dumps(record.metadata),
                ),
                self.dsn,
            )
            connection.commit()
        return record

    def list_recommendations(self, prompt_name: str | None = None, *, limit: int = 20) -> list[PromptRecommendation]:
        placeholder = self._placeholder()
        clauses: list[str] = []
        params: list[Any] = []
        if prompt_name is not None:
            clauses.append(f"prompt_name = {placeholder}")
            params.append(prompt_name)
        where_sql = f"WHERE {' AND '.join(clauses)}" if clauses else ""
        params.append(limit)
        with self._connect() as connection:
            self._ensure_schema(connection)
            rows = _fetchall(
                connection,
                f"SELECT * FROM prompt_recommendations {where_sql} ORDER BY created_at DESC LIMIT {placeholder}",
                params,
                self.dsn,
            )
        return [self._row_to_recommendation(row) for row in rows]

    def resolve(
        self,
        prompt_name: str,
        *,
        workspace_id: str | None = None,
        task_type: str | None = None,
        user_segment: str | None = None,
        environment: str = "prod",
        assignment_key: str | None = None,
    ) -> PromptResolution | None:
        experiment = self._resolve_experiment(
            prompt_name,
            workspace_id=workspace_id,
            task_type=task_type,
            user_segment=user_segment,
            environment=environment,
        )
        if experiment is not None:
            selected_version, variant = self._assign_experiment_variant(experiment, assignment_key)
            version_record = self.get_version(prompt_name, selected_version)
            if version_record is not None:
                return PromptResolution(
                    prompt_name=prompt_name,
                    version=selected_version,
                    content=version_record.content,
                    tags=version_record.tags,
                    experiment_id=experiment.id,
                    variant=variant,
                    workspace_id=workspace_id,
                    task_type=task_type,
                    user_segment=user_segment,
                )

        deployments = self.list_deployments(prompt_name, environment=environment, include_inactive=False)
        scored: list[tuple[int, PromptDeployment]] = []
        for deployment in deployments:
            score = 0
            if deployment.workspace_id is not None and deployment.workspace_id != workspace_id:
                continue
            if deployment.task_type is not None and deployment.task_type != task_type:
                continue
            if deployment.user_segment is not None and deployment.user_segment != user_segment:
                continue
            if deployment.workspace_id is not None:
                score += 4
            if deployment.task_type is not None:
                score += 2
            if deployment.user_segment is not None:
                score += 1
            scored.append((score, deployment))
        if not scored:
            versions = self.list_versions(prompt_name)
            if not versions:
                return None
            latest = versions[0]
            return PromptResolution(
                prompt_name=prompt_name,
                version=latest.version,
                content=latest.content,
                tags=latest.tags,
                workspace_id=workspace_id,
                task_type=task_type,
                user_segment=user_segment,
            )
        _, selected = sorted(scored, key=lambda item: (item[0], item[1].promoted_at), reverse=True)[0]
        version_record = self.get_version(prompt_name, selected.version)
        if version_record is None:
            return None
        return PromptResolution(
            prompt_name=prompt_name,
            version=selected.version,
            content=version_record.content,
            tags=version_record.tags,
            workspace_id=workspace_id,
            task_type=task_type,
            user_segment=user_segment,
        )

    def _resolve_experiment(
        self,
        prompt_name: str,
        *,
        workspace_id: str | None,
        task_type: str | None,
        user_segment: str | None,
        environment: str,
    ) -> PromptExperiment | None:
        candidates = self.list_experiments(prompt_name, status="active")
        scored: list[tuple[int, PromptExperiment]] = []
        for experiment in candidates:
            if experiment.environment != environment:
                continue
            if experiment.workspace_id is not None and experiment.workspace_id != workspace_id:
                continue
            if experiment.task_type is not None and experiment.task_type != task_type:
                continue
            if experiment.user_segment is not None and experiment.user_segment != user_segment:
                continue
            score = 0
            if experiment.workspace_id is not None:
                score += 4
            if experiment.task_type is not None:
                score += 2
            if experiment.user_segment is not None:
                score += 1
            scored.append((score, experiment))
        if not scored:
            return None
        return sorted(scored, key=lambda item: (item[0], item[1].created_at), reverse=True)[0][1]

    def _assign_experiment_variant(self, experiment: PromptExperiment, assignment_key: str | None) -> tuple[int, str]:
        key = assignment_key or uuid4().hex
        digest = hashlib.sha256(f"{experiment.id}:{key}".encode()).hexdigest()
        bucket = int(digest[:8], 16) % 100
        if bucket >= experiment.rollout_percentage:
            return experiment.baseline_version, "baseline"
        variants = [experiment.baseline_version, *experiment.challenger_versions]
        variant_index = int(digest[8:16], 16) % len(variants)
        chosen = variants[variant_index]
        if chosen == experiment.baseline_version:
            return chosen, "baseline"
        return chosen, f"challenger-v{chosen}"

    def _row_to_prompt(self, row: sqlite3.Row | Any) -> PromptDefinition:
        return PromptDefinition(
            name=str(_row_value(row, "name", 0)),
            description=_row_value(row, "description", 1),
            created_at=str(_row_value(row, "created_at", 2)),
        )

    def _row_to_version(self, row: sqlite3.Row | Any) -> PromptVersionRecord:
        tags = _decode_json_field(_row_value(row, "tags_json", 4), default=[])
        return PromptVersionRecord(
            id=str(_row_value(row, "id", 0)),
            prompt_name=str(_row_value(row, "prompt_name", 1)),
            version=int(_row_value(row, "version", 2)),
            content=str(_row_value(row, "content", 3)),
            tags=list(tags) if isinstance(tags, list) else [],
            notes=_row_value(row, "notes", 5),
            created_at=str(_row_value(row, "created_at", 6)),
            created_by=_row_value(row, "created_by", 7),
        )

    def _row_to_deployment(self, row: sqlite3.Row | Any) -> PromptDeployment:
        return PromptDeployment(
            id=str(_row_value(row, "id", 0)),
            prompt_name=str(_row_value(row, "prompt_name", 1)),
            version=int(_row_value(row, "version", 2)),
            environment=str(_row_value(row, "environment", 3)),
            workspace_id=_row_value(row, "workspace_id", 4),
            task_type=_row_value(row, "task_type", 5),
            user_segment=_row_value(row, "user_segment", 6),
            active=bool(_row_value(row, "active", 7)),
            promoted_at=str(_row_value(row, "promoted_at", 8)),
            metadata=_decode_json_field(_row_value(row, "metadata_json", 9), default={}),
        )

    def _row_to_experiment(self, row: sqlite3.Row | Any) -> PromptExperiment:
        return PromptExperiment(
            id=str(_row_value(row, "id", 0)),
            prompt_name=str(_row_value(row, "prompt_name", 1)),
            baseline_version=int(_row_value(row, "baseline_version", 2)),
            challenger_versions=_decode_json_field(_row_value(row, "challenger_versions_json", 3), default=[]),
            metric=str(_row_value(row, "metric", 4)),
            rollout_percentage=int(_row_value(row, "rollout_percentage", 5)),
            environment=str(_row_value(row, "environment", 6)),
            workspace_id=_row_value(row, "workspace_id", 7),
            task_type=_row_value(row, "task_type", 8),
            user_segment=_row_value(row, "user_segment", 9),
            status=str(_row_value(row, "status", 10)),
            created_at=str(_row_value(row, "created_at", 11)),
            ended_at=_row_value(row, "ended_at", 12),
            metadata=_decode_json_field(_row_value(row, "metadata_json", 13), default={}),
        )

    def _row_to_recommendation(self, row: sqlite3.Row | Any) -> PromptRecommendation:
        return PromptRecommendation(
            id=str(_row_value(row, "id", 0)),
            prompt_name=str(_row_value(row, "prompt_name", 1)),
            kind=str(_row_value(row, "kind", 2)),
            title=str(_row_value(row, "title", 3)),
            summary=str(_row_value(row, "summary", 4)),
            proposed_patch=_row_value(row, "proposed_patch", 5),
            based_on_version=_row_value(row, "based_on_version", 6),
            failure_counts=_decode_json_field(_row_value(row, "failure_counts_json", 7), default={}),
            status=str(_row_value(row, "status", 8)),
            created_at=str(_row_value(row, "created_at", 9)),
            metadata=_decode_json_field(_row_value(row, "metadata_json", 10), default={}),
        )


_prompt_registry: PromptRegistry | None = None


def get_prompt_registry() -> PromptRegistry:
    """Return the process-global prompt registry."""
    global _prompt_registry
    if _prompt_registry is None:
        _prompt_registry = PromptRegistry()
    return _prompt_registry


def reset_prompt_registry(registry: PromptRegistry | None = None) -> PromptRegistry:
    """Reset the process-global prompt registry."""
    global _prompt_registry
    _prompt_registry = registry or PromptRegistry()
    return _prompt_registry


def record_to_dict(record: Any) -> dict[str, Any]:
    """Normalize a prompt registry dataclass record to a dictionary."""
    return asdict(record)


def _execute(connection: sqlite3.Connection | Any, query: str, params: tuple[Any, ...] | list[Any], use_postgresql: bool) -> None:
    if use_postgresql:
        with connection.cursor() as cursor:
            cursor.execute(query, tuple(params))
        return
    connection.execute(query, tuple(params))


def _fetchone(connection: sqlite3.Connection | Any, query: str, params: tuple[Any, ...] | list[Any], use_postgresql: bool) -> Any:
    if use_postgresql:
        with connection.cursor() as cursor:
            cursor.execute(query, tuple(params))
            return cursor.fetchone()
    return connection.execute(query, tuple(params)).fetchone()


def _fetchall(connection: sqlite3.Connection | Any, query: str, params: tuple[Any, ...] | list[Any], use_postgresql: bool) -> list[Any]:
    if use_postgresql:
        with connection.cursor() as cursor:
            cursor.execute(query, tuple(params))
            return list(cursor.fetchall())
    return list(connection.execute(query, tuple(params)).fetchall())


def _row_value(row: Any, name: str, index: int) -> Any:
    if row is None:
        return None
    try:
        return row[name]
    except (KeyError, TypeError, IndexError):
        pass
    try:
        return getattr(row, name)
    except AttributeError:
        pass
    try:
        return row[index]
    except (IndexError, TypeError, KeyError):
        return None


def _decode_json_field(value: Any, *, default: Any) -> Any:
    if value in (None, ""):
        return default
    if isinstance(value, (dict, list)):
        return value
    if isinstance(value, str):
        try:
            return json.loads(value)
        except json.JSONDecodeError:
            return default
    return value


__all__ = [
    "PROMPT_REGISTRY_SCHEMA_KIND",
    "PROMPT_REGISTRY_SCHEMA_VERSION",
    "PromptDefinition",
    "PromptVersionRecord",
    "PromptDeployment",
    "PromptExperiment",
    "PromptRecommendation",
    "PromptResolution",
    "PromptRegistry",
    "get_prompt_registry",
    "reset_prompt_registry",
    "record_to_dict",
]
