"""Tool and workflow learning primitives for adaptive runtime behavior."""
from __future__ import annotations

from dataclasses import dataclass, field
import math
import os
from pathlib import Path
import sqlite3
from typing import Any

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

from .models import utcnow


TOOL_WORKFLOW_LEARNING_SCHEMA_KIND = "tool_workflow_learning"
TOOL_WORKFLOW_LEARNING_SCHEMA_VERSION = 1


def _segment(value: str | None) -> str:
    return value or ""


def _score(successes: int, failures: int) -> float:
    total = successes + failures
    if total <= 0:
        return 0.5
    return (successes + 1.0) / (total + 2.0)


@dataclass
class ToolScore:
    """Aggregated tool performance for a task segment."""

    tool_name: str
    task_type: str | None = None
    workspace_id: str | None = None
    success_count: int = 0
    failure_count: int = 0
    average_duration_ms: float = 0.0
    win_rate: float = 0.0
    score: float = 0.0
    last_updated: str = field(default_factory=utcnow)


@dataclass
class FallbackPolicyScore:
    """Aggregated fallback provider effectiveness for a task segment."""

    provider_key: str
    task_type: str | None = None
    workspace_id: str | None = None
    success_count: int = 0
    failure_count: int = 0
    average_duration_ms: float = 0.0
    win_rate: float = 0.0
    score: float = 0.0
    last_updated: str = field(default_factory=utcnow)


@dataclass
class WorkflowBranchScore:
    """Aggregated workflow branch effectiveness."""

    pipeline_name: str
    step_name: str
    branch_name: str
    workspace_id: str | None = None
    success_count: int = 0
    failure_count: int = 0
    average_duration_ms: float = 0.0
    score: float = 0.0
    last_updated: str = field(default_factory=utcnow)


@dataclass
class ApprovalScore:
    """Aggregated approval friction for a step."""

    pipeline_name: str
    step_name: str
    workspace_id: str | None = None
    request_count: int = 0
    approval_count: int = 0
    rejection_count: int = 0
    correction_rate: float = 0.0
    approval_rate: float = 0.0
    last_updated: str = field(default_factory=utcnow)


@dataclass
class LoopRecommendation:
    """Recommended loop limit based on historical executions."""

    pipeline_name: str
    step_name: str
    workspace_id: str | None = None
    recommended_limit: int = 1
    average_iterations: float = 0.0
    max_observed_iterations: int = 0
    max_hits: int = 0
    sample_size: int = 0
    last_updated: str = field(default_factory=utcnow)


class ToolWorkflowLearningEngine:
    """Persistent learning engine for tool and workflow recommendations."""

    def __init__(
        self,
        path: str | None = None,
        table_prefix: str | None = None,
        dsn: str | None = None,
    ) -> None:
        self.path = path or os.getenv(
            "LOGOS_ADK_TOOL_WORKFLOW_DB_PATH",
            ".logos_adk/tool_workflow_learning.db",
        )
        self.dsn = resolve_database_url(
            dsn or os.getenv("LOGOS_ADK_TOOL_WORKFLOW_DSN"),
            env_vars=("LOGOS_ADK_TOOL_WORKFLOW_DSN", "LOGOS_ADK_DATABASE_URL", "DATABASE_URL"),
        )
        self.table_prefix = table_prefix or os.getenv(
            "LOGOS_ADK_TOOL_WORKFLOW_TABLE_PREFIX",
            "tool_workflow_learning",
        )

    def _table(self, suffix: str) -> str:
        return f"{self.table_prefix}_{suffix}"

    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
        self._ensure_schema(connection)
        return connection

    def _ensure_schema(self, connection: sqlite3.Connection | Any) -> None:
        if self.dsn:
            current = get_postgresql_schema_version(connection, TOOL_WORKFLOW_LEARNING_SCHEMA_KIND) or 0
            with connection.cursor() as cursor:
                cursor.execute(
                    f"""
                    CREATE TABLE IF NOT EXISTS {self._table('tool_events')} (
                        id TEXT PRIMARY KEY,
                        workspace_id TEXT NOT NULL,
                        task_type TEXT NOT NULL,
                        tool_name TEXT NOT NULL,
                        success INTEGER NOT NULL,
                        duration_ms DOUBLE PRECISION NOT NULL,
                        error TEXT,
                        trace_id TEXT,
                        created_at TEXT NOT NULL
                    )
                    """
                )
                cursor.execute(
                    f"""
                    CREATE TABLE IF NOT EXISTS {self._table('fallback_events')} (
                        id TEXT PRIMARY KEY,
                        workspace_id TEXT NOT NULL,
                        task_type TEXT NOT NULL,
                        provider_key TEXT NOT NULL,
                        success INTEGER NOT NULL,
                        duration_ms DOUBLE PRECISION NOT NULL,
                        was_primary INTEGER NOT NULL DEFAULT 0,
                        error TEXT,
                        trace_id TEXT,
                        created_at TEXT NOT NULL
                    )
                    """
                )
                cursor.execute(
                    f"""
                    CREATE TABLE IF NOT EXISTS {self._table('workflow_branch_events')} (
                        id TEXT PRIMARY KEY,
                        workspace_id TEXT NOT NULL,
                        pipeline_name TEXT NOT NULL,
                        step_name TEXT NOT NULL,
                        branch_name TEXT NOT NULL,
                        success INTEGER NOT NULL,
                        duration_ms DOUBLE PRECISION NOT NULL,
                        trace_id TEXT,
                        created_at TEXT NOT NULL
                    )
                    """
                )
                cursor.execute(
                    f"""
                    CREATE TABLE IF NOT EXISTS {self._table('loop_events')} (
                        id TEXT PRIMARY KEY,
                        workspace_id TEXT NOT NULL,
                        pipeline_name TEXT NOT NULL,
                        step_name TEXT NOT NULL,
                        iterations INTEGER NOT NULL,
                        max_iterations INTEGER NOT NULL,
                        termination_reason TEXT NOT NULL,
                        success INTEGER NOT NULL,
                        trace_id TEXT,
                        created_at TEXT NOT NULL
                    )
                    """
                )
                cursor.execute(
                    f"""
                    CREATE TABLE IF NOT EXISTS {self._table('approval_events')} (
                        id TEXT PRIMARY KEY,
                        workspace_id TEXT NOT NULL,
                        pipeline_name TEXT NOT NULL,
                        step_name TEXT NOT NULL,
                        event_type TEXT NOT NULL,
                        approved INTEGER,
                        corrected INTEGER,
                        trace_id TEXT,
                        created_at TEXT NOT NULL
                    )
                    """
                )
                cursor.execute(
                    f"CREATE INDEX IF NOT EXISTS idx_{self._table('tool_events')}_segment ON {self._table('tool_events')} (workspace_id, task_type, tool_name, created_at DESC)"
                )
                cursor.execute(
                    f"CREATE INDEX IF NOT EXISTS idx_{self._table('fallback_events')}_segment ON {self._table('fallback_events')} (workspace_id, task_type, provider_key, created_at DESC)"
                )
                cursor.execute(
                    f"CREATE INDEX IF NOT EXISTS idx_{self._table('workflow_branch_events')}_segment ON {self._table('workflow_branch_events')} (workspace_id, pipeline_name, step_name, branch_name, created_at DESC)"
                )
                cursor.execute(
                    f"CREATE INDEX IF NOT EXISTS idx_{self._table('loop_events')}_segment ON {self._table('loop_events')} (workspace_id, pipeline_name, step_name, created_at DESC)"
                )
                cursor.execute(
                    f"CREATE INDEX IF NOT EXISTS idx_{self._table('approval_events')}_segment ON {self._table('approval_events')} (workspace_id, pipeline_name, step_name, created_at DESC)"
                )
            if current < TOOL_WORKFLOW_LEARNING_SCHEMA_VERSION:
                set_postgresql_schema_version(
                    connection,
                    TOOL_WORKFLOW_LEARNING_SCHEMA_KIND,
                    TOOL_WORKFLOW_LEARNING_SCHEMA_VERSION,
                )
            connection.commit()
            return

        current = get_sqlite_schema_version(connection, TOOL_WORKFLOW_LEARNING_SCHEMA_KIND) or 0
        connection.execute(
            f"""
            CREATE TABLE IF NOT EXISTS {self._table('tool_events')} (
                id TEXT PRIMARY KEY,
                workspace_id TEXT NOT NULL,
                task_type TEXT NOT NULL,
                tool_name TEXT NOT NULL,
                success INTEGER NOT NULL,
                duration_ms REAL NOT NULL,
                error TEXT,
                trace_id TEXT,
                created_at TEXT NOT NULL
            )
            """
        )
        connection.execute(
            f"""
            CREATE TABLE IF NOT EXISTS {self._table('fallback_events')} (
                id TEXT PRIMARY KEY,
                workspace_id TEXT NOT NULL,
                task_type TEXT NOT NULL,
                provider_key TEXT NOT NULL,
                success INTEGER NOT NULL,
                duration_ms REAL NOT NULL,
                was_primary INTEGER NOT NULL DEFAULT 0,
                error TEXT,
                trace_id TEXT,
                created_at TEXT NOT NULL
            )
            """
        )
        connection.execute(
            f"""
            CREATE TABLE IF NOT EXISTS {self._table('workflow_branch_events')} (
                id TEXT PRIMARY KEY,
                workspace_id TEXT NOT NULL,
                pipeline_name TEXT NOT NULL,
                step_name TEXT NOT NULL,
                branch_name TEXT NOT NULL,
                success INTEGER NOT NULL,
                duration_ms REAL NOT NULL,
                trace_id TEXT,
                created_at TEXT NOT NULL
            )
            """
        )
        connection.execute(
            f"""
            CREATE TABLE IF NOT EXISTS {self._table('loop_events')} (
                id TEXT PRIMARY KEY,
                workspace_id TEXT NOT NULL,
                pipeline_name TEXT NOT NULL,
                step_name TEXT NOT NULL,
                iterations INTEGER NOT NULL,
                max_iterations INTEGER NOT NULL,
                termination_reason TEXT NOT NULL,
                success INTEGER NOT NULL,
                trace_id TEXT,
                created_at TEXT NOT NULL
            )
            """
        )
        connection.execute(
            f"""
            CREATE TABLE IF NOT EXISTS {self._table('approval_events')} (
                id TEXT PRIMARY KEY,
                workspace_id TEXT NOT NULL,
                pipeline_name TEXT NOT NULL,
                step_name TEXT NOT NULL,
                event_type TEXT NOT NULL,
                approved INTEGER,
                corrected INTEGER,
                trace_id TEXT,
                created_at TEXT NOT NULL
            )
            """
        )
        connection.execute(
            f"CREATE INDEX IF NOT EXISTS idx_{self._table('tool_events')}_segment ON {self._table('tool_events')} (workspace_id, task_type, tool_name, created_at DESC)"
        )
        connection.execute(
            f"CREATE INDEX IF NOT EXISTS idx_{self._table('fallback_events')}_segment ON {self._table('fallback_events')} (workspace_id, task_type, provider_key, created_at DESC)"
        )
        connection.execute(
            f"CREATE INDEX IF NOT EXISTS idx_{self._table('workflow_branch_events')}_segment ON {self._table('workflow_branch_events')} (workspace_id, pipeline_name, step_name, branch_name, created_at DESC)"
        )
        connection.execute(
            f"CREATE INDEX IF NOT EXISTS idx_{self._table('loop_events')}_segment ON {self._table('loop_events')} (workspace_id, pipeline_name, step_name, created_at DESC)"
        )
        connection.execute(
            f"CREATE INDEX IF NOT EXISTS idx_{self._table('approval_events')}_segment ON {self._table('approval_events')} (workspace_id, pipeline_name, step_name, created_at DESC)"
        )
        if current < TOOL_WORKFLOW_LEARNING_SCHEMA_VERSION:
            set_sqlite_schema_version(
                connection,
                TOOL_WORKFLOW_LEARNING_SCHEMA_KIND,
                TOOL_WORKFLOW_LEARNING_SCHEMA_VERSION,
            )
        connection.commit()

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

    def record_tool_call(
        self,
        *,
        tool_name: str,
        success: bool,
        duration_ms: float,
        task_type: str | None = None,
        workspace_id: str | None = None,
        trace_id: str | None = None,
        error: str | None = None,
    ) -> None:
        placeholder = self._placeholder()
        with self._connect() as connection:
            _execute(
                connection,
                f"""
                INSERT INTO {self._table('tool_events')}
                (id, workspace_id, task_type, tool_name, success, duration_ms, error, trace_id, created_at)
                VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder})
                """,
                (
                    self._make_id(),
                    _segment(workspace_id),
                    _segment(task_type),
                    tool_name,
                    1 if success else 0,
                    max(0.0, float(duration_ms)),
                    error,
                    trace_id,
                    utcnow(),
                ),
                self.dsn,
            )

    def record_fallback(
        self,
        *,
        provider_key: str,
        success: bool,
        duration_ms: float,
        task_type: str | None = None,
        workspace_id: str | None = None,
        trace_id: str | None = None,
        was_primary: bool = False,
        error: str | None = None,
    ) -> None:
        placeholder = self._placeholder()
        with self._connect() as connection:
            _execute(
                connection,
                f"""
                INSERT INTO {self._table('fallback_events')}
                (id, workspace_id, task_type, provider_key, success, duration_ms, was_primary, error, trace_id, created_at)
                VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder})
                """,
                (
                    self._make_id(),
                    _segment(workspace_id),
                    _segment(task_type),
                    provider_key,
                    1 if success else 0,
                    max(0.0, float(duration_ms)),
                    1 if was_primary else 0,
                    error,
                    trace_id,
                    utcnow(),
                ),
                self.dsn,
            )

    def record_workflow_branch(
        self,
        *,
        pipeline_name: str,
        step_name: str,
        branch_name: str,
        success: bool,
        duration_ms: float,
        workspace_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        placeholder = self._placeholder()
        with self._connect() as connection:
            _execute(
                connection,
                f"""
                INSERT INTO {self._table('workflow_branch_events')}
                (id, workspace_id, pipeline_name, step_name, branch_name, success, duration_ms, trace_id, created_at)
                VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder})
                """,
                (
                    self._make_id(),
                    _segment(workspace_id),
                    pipeline_name,
                    step_name,
                    branch_name,
                    1 if success else 0,
                    max(0.0, float(duration_ms)),
                    trace_id,
                    utcnow(),
                ),
                self.dsn,
            )

    def record_loop_execution(
        self,
        *,
        pipeline_name: str,
        step_name: str,
        iterations: int,
        max_iterations: int,
        termination_reason: str,
        success: bool,
        workspace_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        placeholder = self._placeholder()
        with self._connect() as connection:
            _execute(
                connection,
                f"""
                INSERT INTO {self._table('loop_events')}
                (id, workspace_id, pipeline_name, step_name, iterations, max_iterations, termination_reason, success, trace_id, created_at)
                VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder})
                """,
                (
                    self._make_id(),
                    _segment(workspace_id),
                    pipeline_name,
                    step_name,
                    max(0, int(iterations)),
                    max(1, int(max_iterations)),
                    termination_reason,
                    1 if success else 0,
                    trace_id,
                    utcnow(),
                ),
                self.dsn,
            )

    def record_approval_request(
        self,
        *,
        pipeline_name: str,
        step_name: str,
        workspace_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        self._record_approval_event(
            pipeline_name=pipeline_name,
            step_name=step_name,
            workspace_id=workspace_id,
            trace_id=trace_id,
            event_type="request",
            approved=None,
            corrected=None,
        )

    def record_approval_outcome(
        self,
        *,
        pipeline_name: str,
        step_name: str,
        approved: bool,
        corrected: bool,
        workspace_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        self._record_approval_event(
            pipeline_name=pipeline_name,
            step_name=step_name,
            workspace_id=workspace_id,
            trace_id=trace_id,
            event_type="outcome",
            approved=approved,
            corrected=corrected,
        )

    def _record_approval_event(
        self,
        *,
        pipeline_name: str,
        step_name: str,
        workspace_id: str | None,
        trace_id: str | None,
        event_type: str,
        approved: bool | None,
        corrected: bool | None,
    ) -> None:
        placeholder = self._placeholder()
        with self._connect() as connection:
            _execute(
                connection,
                f"""
                INSERT INTO {self._table('approval_events')}
                (id, workspace_id, pipeline_name, step_name, event_type, approved, corrected, trace_id, created_at)
                VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder})
                """,
                (
                    self._make_id(),
                    _segment(workspace_id),
                    pipeline_name,
                    step_name,
                    event_type,
                    None if approved is None else (1 if approved else 0),
                    None if corrected is None else (1 if corrected else 0),
                    trace_id,
                    utcnow(),
                ),
                self.dsn,
            )

    def list_tool_scores(
        self,
        *,
        task_type: str | None = None,
        workspace_id: str | None = None,
    ) -> list[ToolScore]:
        placeholder = self._placeholder()
        with self._connect() as connection:
            rows = _fetchall(
                connection,
                f"""
                SELECT
                    tool_name,
                    workspace_id,
                    task_type,
                    SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS success_count,
                    SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) AS failure_count,
                    AVG(duration_ms) AS average_duration_ms,
                    MAX(created_at) AS last_updated
                FROM {self._table('tool_events')}
                WHERE workspace_id = {placeholder} AND task_type = {placeholder}
                GROUP BY workspace_id, task_type, tool_name
                """,
                (_segment(workspace_id), _segment(task_type)),
                self.dsn,
            )
        scores: list[ToolScore] = []
        for row in rows:
            success_count = int(_row_value(row, "success_count", 3) or 0)
            failure_count = int(_row_value(row, "failure_count", 4) or 0)
            win_rate = success_count / max(1, success_count + failure_count)
            scores.append(
                ToolScore(
                    tool_name=str(_row_value(row, "tool_name", 0)),
                    workspace_id=workspace_id,
                    task_type=task_type,
                    success_count=success_count,
                    failure_count=failure_count,
                    average_duration_ms=float(_row_value(row, "average_duration_ms", 5) or 0.0),
                    win_rate=win_rate,
                    score=_score(success_count, failure_count),
                    last_updated=str(_row_value(row, "last_updated", 6) or utcnow()),
                )
            )
        scores.sort(key=lambda item: (-item.score, -item.win_rate, item.average_duration_ms, item.tool_name))
        return scores

    def recommended_tool_names(
        self,
        tool_names: list[str],
        *,
        task_type: str | None = None,
        workspace_id: str | None = None,
    ) -> list[str]:
        score_map = {
            item.tool_name: item
            for item in self.list_tool_scores(task_type=task_type, workspace_id=workspace_id)
        }
        return sorted(
            list(tool_names),
            key=lambda name: (
                -(score_map.get(name).score if name in score_map else 0.5),
                score_map.get(name).average_duration_ms if name in score_map else 0.0,
                tool_names.index(name),
            ),
        )

    def list_fallback_scores(
        self,
        *,
        task_type: str | None = None,
        workspace_id: str | None = None,
    ) -> list[FallbackPolicyScore]:
        placeholder = self._placeholder()
        with self._connect() as connection:
            rows = _fetchall(
                connection,
                f"""
                SELECT
                    provider_key,
                    workspace_id,
                    task_type,
                    SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS success_count,
                    SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) AS failure_count,
                    AVG(duration_ms) AS average_duration_ms,
                    MAX(created_at) AS last_updated
                FROM {self._table('fallback_events')}
                WHERE workspace_id = {placeholder} AND task_type = {placeholder} AND was_primary = 0
                GROUP BY workspace_id, task_type, provider_key
                """,
                (_segment(workspace_id), _segment(task_type)),
                self.dsn,
            )
        scores: list[FallbackPolicyScore] = []
        for row in rows:
            success_count = int(_row_value(row, "success_count", 3) or 0)
            failure_count = int(_row_value(row, "failure_count", 4) or 0)
            win_rate = success_count / max(1, success_count + failure_count)
            scores.append(
                FallbackPolicyScore(
                    provider_key=str(_row_value(row, "provider_key", 0)),
                    workspace_id=workspace_id,
                    task_type=task_type,
                    success_count=success_count,
                    failure_count=failure_count,
                    average_duration_ms=float(_row_value(row, "average_duration_ms", 5) or 0.0),
                    win_rate=win_rate,
                    score=_score(success_count, failure_count),
                    last_updated=str(_row_value(row, "last_updated", 6) or utcnow()),
                )
            )
        scores.sort(
            key=lambda item: (-item.score, -item.win_rate, item.average_duration_ms, item.provider_key)
        )
        return scores

    def recommended_fallback_keys(
        self,
        provider_keys: list[str],
        *,
        task_type: str | None = None,
        workspace_id: str | None = None,
    ) -> list[str]:
        score_map = {
            item.provider_key: item
            for item in self.list_fallback_scores(task_type=task_type, workspace_id=workspace_id)
        }
        return sorted(
            list(provider_keys),
            key=lambda key: (
                -(score_map.get(key).score if key in score_map else 0.5),
                score_map.get(key).average_duration_ms if key in score_map else 0.0,
                provider_keys.index(key),
            ),
        )

    def list_workflow_branch_scores(
        self,
        *,
        pipeline_name: str | None = None,
        step_name: str | None = None,
        workspace_id: str | None = None,
    ) -> list[WorkflowBranchScore]:
        placeholder = self._placeholder()
        clauses = [f"workspace_id = {placeholder}"]
        params: list[Any] = [_segment(workspace_id)]
        if pipeline_name is not None:
            clauses.append(f"pipeline_name = {placeholder}")
            params.append(pipeline_name)
        if step_name is not None:
            clauses.append(f"step_name = {placeholder}")
            params.append(step_name)
        query = f"""
            SELECT
                pipeline_name,
                step_name,
                branch_name,
                SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS success_count,
                SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) AS failure_count,
                AVG(duration_ms) AS average_duration_ms,
                MAX(created_at) AS last_updated
            FROM {self._table('workflow_branch_events')}
            WHERE {' AND '.join(clauses)}
            GROUP BY workspace_id, pipeline_name, step_name, branch_name
        """
        with self._connect() as connection:
            rows = _fetchall(connection, query, tuple(params), self.dsn)
        scores: list[WorkflowBranchScore] = []
        for row in rows:
            success_count = int(_row_value(row, "success_count", 3) or 0)
            failure_count = int(_row_value(row, "failure_count", 4) or 0)
            scores.append(
                WorkflowBranchScore(
                    pipeline_name=str(_row_value(row, "pipeline_name", 0)),
                    step_name=str(_row_value(row, "step_name", 1)),
                    branch_name=str(_row_value(row, "branch_name", 2)),
                    workspace_id=workspace_id,
                    success_count=success_count,
                    failure_count=failure_count,
                    average_duration_ms=float(_row_value(row, "average_duration_ms", 5) or 0.0),
                    score=_score(success_count, failure_count),
                    last_updated=str(_row_value(row, "last_updated", 6) or utcnow()),
                )
            )
        scores.sort(key=lambda item: (-item.score, item.average_duration_ms, item.branch_name))
        return scores

    def recommend_loop_limit(
        self,
        pipeline_name: str,
        step_name: str,
        *,
        base_limit: int,
        workspace_id: str | None = None,
    ) -> int:
        placeholder = self._placeholder()
        with self._connect() as connection:
            rows = _fetchall(
                connection,
                f"""
                SELECT iterations, max_iterations, termination_reason
                FROM {self._table('loop_events')}
                WHERE workspace_id = {placeholder} AND pipeline_name = {placeholder} AND step_name = {placeholder}
                ORDER BY created_at DESC
                LIMIT 50
                """,
                (_segment(workspace_id), pipeline_name, step_name),
                self.dsn,
            )
        if not rows:
            return max(1, int(base_limit))
        avg_iterations = sum(int(_row_value(row, "iterations", 0) or 0) for row in rows) / max(1, len(rows))
        max_hits = sum(1 for row in rows if _row_value(row, "termination_reason", 2) == "max_iterations")
        if max_hits >= max(2, len(rows) // 3):
            return max(int(base_limit), min(int(base_limit * 2), math.ceil(avg_iterations + 1)))
        target = max(1, math.ceil(avg_iterations + 1))
        return min(max(1, int(base_limit)), target)

    def list_approval_scores(
        self,
        *,
        pipeline_name: str | None = None,
        step_name: str | None = None,
        workspace_id: str | None = None,
    ) -> list[ApprovalScore]:
        placeholder = self._placeholder()
        clauses = [f"workspace_id = {placeholder}"]
        params: list[Any] = [_segment(workspace_id)]
        if pipeline_name is not None:
            clauses.append(f"pipeline_name = {placeholder}")
            params.append(pipeline_name)
        if step_name is not None:
            clauses.append(f"step_name = {placeholder}")
            params.append(step_name)
        query = f"""
            SELECT
                pipeline_name,
                step_name,
                SUM(CASE WHEN event_type = 'request' THEN 1 ELSE 0 END) AS request_count,
                SUM(CASE WHEN event_type = 'outcome' AND approved = 1 THEN 1 ELSE 0 END) AS approval_count,
                SUM(CASE WHEN event_type = 'outcome' AND approved = 0 THEN 1 ELSE 0 END) AS rejection_count,
                SUM(CASE WHEN event_type = 'outcome' AND corrected = 1 THEN 1 ELSE 0 END) AS correction_count,
                MAX(created_at) AS last_updated
            FROM {self._table('approval_events')}
            WHERE {' AND '.join(clauses)}
            GROUP BY workspace_id, pipeline_name, step_name
        """
        with self._connect() as connection:
            rows = _fetchall(connection, query, tuple(params), self.dsn)
        scores: list[ApprovalScore] = []
        for row in rows:
            request_count = int(_row_value(row, "request_count", 2) or 0)
            approval_count = int(_row_value(row, "approval_count", 3) or 0)
            rejection_count = int(_row_value(row, "rejection_count", 4) or 0)
            correction_count = int(_row_value(row, "correction_count", 5) or 0)
            outcomes = max(1, approval_count + rejection_count)
            scores.append(
                ApprovalScore(
                    pipeline_name=str(_row_value(row, "pipeline_name", 0)),
                    step_name=str(_row_value(row, "step_name", 1)),
                    workspace_id=workspace_id,
                    request_count=request_count,
                    approval_count=approval_count,
                    rejection_count=rejection_count,
                    correction_rate=correction_count / outcomes,
                    approval_rate=approval_count / outcomes,
                    last_updated=str(_row_value(row, "last_updated", 6) or utcnow()),
                )
            )
        scores.sort(key=lambda item: (-item.correction_rate, item.approval_rate, item.step_name))
        return scores

    def list_loop_recommendations(
        self,
        *,
        pipeline_name: str | None = None,
        workspace_id: str | None = None,
    ) -> list[LoopRecommendation]:
        placeholder = self._placeholder()
        clauses = [f"workspace_id = {placeholder}"]
        params: list[Any] = [_segment(workspace_id)]
        if pipeline_name is not None:
            clauses.append(f"pipeline_name = {placeholder}")
            params.append(pipeline_name)
        query = f"""
            SELECT
                pipeline_name,
                step_name,
                AVG(iterations) AS average_iterations,
                MAX(iterations) AS max_observed_iterations,
                SUM(CASE WHEN termination_reason = 'max_iterations' THEN 1 ELSE 0 END) AS max_hits,
                MAX(max_iterations) AS latest_max_iterations,
                COUNT(*) AS sample_size,
                MAX(created_at) AS last_updated
            FROM {self._table('loop_events')}
            WHERE {' AND '.join(clauses)}
            GROUP BY workspace_id, pipeline_name, step_name
        """
        with self._connect() as connection:
            rows = _fetchall(connection, query, tuple(params), self.dsn)
        items: list[LoopRecommendation] = []
        for row in rows:
            base_limit = int(_row_value(row, "latest_max_iterations", 5) or 1)
            items.append(
                LoopRecommendation(
                    pipeline_name=str(_row_value(row, "pipeline_name", 0)),
                    step_name=str(_row_value(row, "step_name", 1)),
                    workspace_id=workspace_id,
                    recommended_limit=self.recommend_loop_limit(
                        str(_row_value(row, "pipeline_name", 0)),
                        str(_row_value(row, "step_name", 1)),
                        base_limit=base_limit,
                        workspace_id=workspace_id,
                    ),
                    average_iterations=float(_row_value(row, "average_iterations", 2) or 0.0),
                    max_observed_iterations=int(_row_value(row, "max_observed_iterations", 3) or 0),
                    max_hits=int(_row_value(row, "max_hits", 4) or 0),
                    sample_size=int(_row_value(row, "sample_size", 6) or 0),
                    last_updated=str(_row_value(row, "last_updated", 7) or utcnow()),
                )
            )
        items.sort(key=lambda item: (-item.recommended_limit, -item.average_iterations, item.step_name))
        return items

    def _make_id(self) -> str:
        from uuid import uuid4

        return uuid4().hex


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 _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


__all__ = [
    "TOOL_WORKFLOW_LEARNING_SCHEMA_KIND",
    "TOOL_WORKFLOW_LEARNING_SCHEMA_VERSION",
    "ApprovalScore",
    "FallbackPolicyScore",
    "LoopRecommendation",
    "ToolScore",
    "ToolWorkflowLearningEngine",
    "WorkflowBranchScore",
]
