use std::fs;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

use anyhow::{Context, Result};
use rusqlite::{Connection, OpenFlags};
use serde::{Deserialize, Serialize};

use crate::db;
use crate::handoff::{self, BranchMode, CheckoutStateView, GitState};
use crate::output::{self, CommandReport, WorkStreamView};
use crate::paths::state::StateLayout;
use crate::profile::{self, ProfileName, DEFAULT_PROFILE};
use crate::repo::marker as repo_marker;
use crate::repo::registry as repo_registry;
use crate::state::escalation as escalation_state;
use crate::state::machine_presence;
use crate::state::machine_registry as pod_identity;
use crate::state::runtime::RuntimeHandoffState;
use crate::state::session as session_state;
use crate::state::session_gates;
use crate::telemetry::{cost as telemetry_cost, host as host_telemetry};

#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
enum StatusPhase {
    BootstrapRequired,
    StartRequired,
    ReadyForSession,
    SessionStale,
    SessionActive,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
enum ReviewStatus {
    Idle,
    Available,
    Recommended,
    AttentionRequired,
}

#[derive(Serialize)]
pub struct StatusReport {
    command: &'static str,
    ok: bool,
    path: String,
    profile: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    project_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    locality_id: Option<String>,
    workspace_path: String,
    work_stream: WorkStreamView,
    phase: StatusPhase,
    phase_reason: String,
    blocked: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    recommended_next_command: Option<String>,
    bootstrap: BootstrapState,
    machine_identity: pod_identity::MachineIdentityView,
    machine_presence: machine_presence::MachinePresenceView,
    execution_context: machine_presence::MachineExecutionContextView,
    takeover_preconditions: machine_presence::MachineTakeoverPreconditionsView,
    coordination_scope: pod_identity::CoordinationScopeView,
    git: Option<GitState>,
    checkout_state: Option<CheckoutStateView>,
    continuity: ContinuityView,
    session: StatusSessionView,
    execution_gates: session_gates::ExecutionGatesView,
    escalation: escalation_state::EscalationView,
    telemetry: StatusTelemetryView,
    review: StatusReviewView,
    warnings: Vec<String>,
}

#[derive(Serialize)]
struct BootstrapState {
    status: &'static str,
    profile_ready: bool,
    linked: bool,
    registry_ready: bool,
    blockers: Vec<String>,
}

#[derive(Serialize)]
struct ContinuityView {
    path: String,
    status: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    title: Option<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    immediate_actions: Vec<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    definition_of_done: Vec<String>,
}

#[derive(Serialize)]
struct StatusSessionView {
    path: String,
    status: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    session_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    mode: Option<session_state::SessionMode>,
    #[serde(skip_serializing_if = "Option::is_none")]
    started_at_epoch_s: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    last_started_at_epoch_s: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    start_count: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    session_minutes: Option<u64>,
    #[serde(flatten)]
    lifecycle: session_state::SessionLifecycleProjection,
}

#[derive(Serialize)]
struct StatusTelemetryView {
    status: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    host: Option<&'static str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    observed_at_epoch_s: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    model: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    context_used_pct: Option<u8>,
    #[serde(skip_serializing_if = "Option::is_none")]
    total_tokens: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    context_window_tokens: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    compacted: Option<bool>,
    cost: telemetry_cost::TelemetryCostView,
}

#[derive(Serialize)]
struct StatusReviewView {
    status: ReviewStatus,
    summary: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    recommended_command: Option<String>,
}

struct ReadOnlyState<T> {
    path: PathBuf,
    value: Option<T>,
}

enum ReadOnlyDbState<T> {
    MissingTable,
    Loaded(T),
}

#[derive(Deserialize)]
struct LegacyCloneRuntimeState {
    handoff: RuntimeHandoffState,
}

impl CommandReport for StatusReport {
    fn exit_code(&self) -> ExitCode {
        ExitCode::SUCCESS
    }

    fn render_text(&self) {
        println!("Status: {}.", phase_label(self.phase));
        println!("{}", self.phase_reason);

        if !self.bootstrap.blockers.is_empty() {
            println!("Bootstrap blockers:");
            for blocker in &self.bootstrap.blockers {
                println!("- {blocker}");
            }
        }

        if let Some(title) = &self.continuity.title {
            println!("Current focus: {title}");
        }

        if !self.continuity.immediate_actions.is_empty() {
            println!("Next actions:");
            for (index, step) in self.continuity.immediate_actions.iter().take(3).enumerate() {
                println!("{}. {step}", index + 1);
            }
        }

        if let Some(git) = &self.git {
            println!("Workspace: {}", summarize_git_workspace(git));
        } else {
            println!("Workspace: unavailable by design for directory-substrate workspaces");
        }
        if let Some(checkout_state) = &self.checkout_state {
            if checkout_state.advisory {
                println!("Workspace note: {}", checkout_state.summary);
            }
        }

        match &self.recommended_next_command {
            Some(command) => println!("Next CCD command: {command}"),
            None => println!("Next CCD command: none required right now"),
        }

        for warning in &self.warnings {
            println!("Warning: {warning}");
        }
    }
}

fn summarize_git_workspace(git: &GitState) -> String {
    let mut summary = format!("{} at {}", git.branch, git.head);
    let tracking = match (git.upstream.as_deref(), git.ahead, git.behind) {
        (Some(upstream), 0, 0) => format!("tracking {upstream} in sync"),
        (Some(upstream), ahead, 0) => format!("tracking {upstream}, ahead {ahead}"),
        (Some(upstream), 0, behind) => format!("tracking {upstream}, behind {behind}"),
        (Some(upstream), ahead, behind) => {
            format!("tracking {upstream}, ahead {ahead}, behind {behind}")
        }
        (None, _, _) => "no upstream".to_owned(),
    };
    summary.push_str(&format!(" ({tracking})"));
    if git.clean {
        summary.push_str(", clean");
    } else {
        summary.push_str(", dirty");
    }
    summary
}

pub fn run(repo_root: &Path, explicit_profile: Option<&str>) -> Result<StatusReport> {
    let profile = profile::resolve(explicit_profile)?;
    let layout = StateLayout::resolve(repo_root, profile.clone())?;
    let marker = repo_marker::load(repo_root)?;
    let locality_id = marker.as_ref().map(|value| value.locality_id.clone());
    let machine_identity = pod_identity::resolve_machine_identity_view(&layout)?;
    let machine_presence = machine_presence::resolve_machine_presence_view(&layout)?;
    let execution_context = machine_presence::build_execution_context_view(
        &layout,
        locality_id.as_deref(),
        &machine_identity,
        &machine_presence,
        None,
    );
    let takeover_preconditions = machine_presence::build_takeover_preconditions_view(
        &execution_context,
        None,
        false,
        0,
        true,
    );
    let coordination_scope = match locality_id.as_deref() {
        Some(locality_id) => pod_identity::resolve_coordination_scope_view(&layout, locality_id)?,
        None => pod_identity::CoordinationScopeView {
            status: "missing",
            name: None,
            source: None,
            config_path: None,
            shared_root: None,
        },
    };
    let bootstrap = build_bootstrap_state(
        repo_root,
        &layout,
        &profile,
        explicit_profile,
        locality_id.as_deref(),
    )?;
    let git = if layout.resolved_substrate().is_git() {
        handoff::read_git_state(repo_root, BranchMode::AllowDetachedHead).ok()
    } else {
        None
    };
    let checkout_state = git
        .as_ref()
        .map(|git| handoff::checkout_state_view(repo_root, git));
    let default_execution_gates = session_gates::build_view(&layout, None);
    let default_escalation = escalation_state::build_view(&layout, &[]);

    if bootstrap.status == "blocked" {
        let phase_reason = bootstrap
            .blockers
            .first()
            .cloned()
            .unwrap_or_else(|| "CCD bootstrap is incomplete".to_owned());
        let warnings = checkout_state
            .as_ref()
            .filter(|state| state.advisory)
            .map(|state| vec![state.summary.clone()])
            .unwrap_or_default();
        return Ok(StatusReport {
            command: "status",
            ok: true,
            path: repo_root.display().to_string(),
            profile: profile.to_string(),
            project_id: locality_id.clone(),
            locality_id: locality_id.clone(),
            workspace_path: repo_root.display().to_string(),
            work_stream: output::work_stream_view(git.as_ref()),
            phase: StatusPhase::BootstrapRequired,
            phase_reason,
            blocked: true,
            recommended_next_command: recommended_bootstrap_command(
                locality_id.as_deref(),
                &profile,
                explicit_profile,
                bootstrap.profile_ready,
                bootstrap.linked,
                bootstrap.registry_ready,
            ),
            bootstrap,
            machine_identity,
            machine_presence,
            execution_context,
            takeover_preconditions,
            coordination_scope,
            git,
            checkout_state,
            continuity: ContinuityView {
                path: layout.state_db_path().display().to_string(),
                status: "missing",
                title: None,
                immediate_actions: Vec::new(),
                definition_of_done: Vec::new(),
            },
            session: StatusSessionView {
                path: layout.state_db_path().display().to_string(),
                status: "missing",
                session_id: None,
                mode: None,
                started_at_epoch_s: None,
                last_started_at_epoch_s: None,
                start_count: None,
                session_minutes: None,
                lifecycle: session_state::SessionLifecycleProjection::missing(),
            },
            execution_gates: default_execution_gates,
            escalation: default_escalation,
            telemetry: StatusTelemetryView {
                status: "unavailable",
                host: None,
                observed_at_epoch_s: None,
                model: None,
                context_used_pct: None,
                total_tokens: None,
                context_window_tokens: None,
                compacted: None,
                cost: telemetry_cost::TelemetryCostView::unavailable(
                    "bootstrap_blocked",
                    "bootstrap must be repaired before telemetry can be summarized",
                ),
            },
            review: StatusReviewView {
                status: ReviewStatus::Idle,
                summary: "status is still in bootstrap mode; close-out review is not available yet"
                    .to_owned(),
                recommended_command: None,
            },
            warnings,
        });
    }

    let locality_id = locality_id.expect("bootstrap_ready implies locality_id");
    let continuity_state = read_handoff_state_read_only(&layout)?;
    let continuity = build_continuity_view(&continuity_state.path, continuity_state.value.as_ref());
    let now_epoch_s = session_state::now_epoch_s()?;
    let tracked_session = read_session_state_read_only(&layout)?;
    let tracked_activity = session_state::load_activity_for_layout(&layout)?;
    let session = build_session_view(
        &tracked_session.path,
        tracked_session.value.as_ref(),
        tracked_activity.as_ref(),
        now_epoch_s,
    );
    let machine_presence = machine_presence::resolve_machine_presence_view(&layout)?;
    let execution_context = machine_presence::build_execution_context_view(
        &layout,
        Some(&locality_id),
        &machine_identity,
        &machine_presence,
        Some(&session.lifecycle),
    );
    let execution_gates =
        session_gates::build_view(&layout, read_execution_gates_state_read_only(&layout)?);
    let escalation_entries = read_escalations_read_only(&layout)?;
    let escalation = escalation_state::build_view(&layout, &escalation_entries);
    let takeover_preconditions = machine_presence::build_takeover_preconditions_view(
        &execution_context,
        Some(&session.lifecycle),
        escalation.blocking_count > 0,
        escalation.blocking_count,
        true,
    );
    let host_snapshot = host_telemetry::current(repo_root).ok().flatten();
    let telemetry = build_telemetry_view(
        &layout,
        &locality_id,
        session.session_id.as_deref(),
        host_snapshot.as_ref(),
        &continuity,
        &execution_gates,
    )?;
    let (phase, phase_reason, recommended_next_command) = derive_phase(
        &profile,
        explicit_profile,
        continuity.status,
        session.status,
    );
    let review = build_review_view(
        &profile,
        explicit_profile,
        git.as_ref(),
        &session,
        &escalation,
        &continuity,
    );
    let mut warnings = Vec::new();
    if let Some(checkout_state) = checkout_state.as_ref().filter(|state| state.advisory) {
        warnings.push(checkout_state.summary.clone());
    }
    if session.status == "stale" {
        warnings.push(format!(
            "session telemetry at {} is stale; refresh it before relying on long-session context health",
            session.path
        ));
    }
    if escalation.blocking_count > 0 {
        warnings.push(format!(
            "{} blocking escalation(s) are active in workspace-local state",
            escalation.blocking_count
        ));
    }

    Ok(StatusReport {
        command: "status",
        ok: true,
        path: repo_root.display().to_string(),
        profile: profile.to_string(),
        project_id: Some(locality_id.clone()),
        locality_id: Some(locality_id),
        workspace_path: repo_root.display().to_string(),
        work_stream: output::work_stream_view(git.as_ref()),
        phase,
        phase_reason,
        blocked: false,
        recommended_next_command,
        bootstrap,
        machine_identity,
        machine_presence,
        execution_context,
        takeover_preconditions,
        coordination_scope,
        git,
        checkout_state,
        continuity,
        session,
        execution_gates,
        escalation,
        telemetry,
        review,
        warnings,
    })
}

fn build_bootstrap_state(
    repo_root: &Path,
    layout: &StateLayout,
    profile: &ProfileName,
    explicit_profile: Option<&str>,
    locality_id: Option<&str>,
) -> Result<BootstrapState> {
    let profile_ready = layout.profile_root().is_dir();
    let linked = locality_id.is_some();
    let registry_ready = match locality_id {
        Some(locality_id) => {
            repo_registry::load(&layout.repo_metadata_path(locality_id)?)?.is_some()
        }
        None => false,
    };
    let mut blockers = Vec::new();
    let attach_command = with_profile_flag("ccd attach --path .", profile, explicit_profile);
    let link_command = with_profile_flag("ccd link --path .", profile, explicit_profile);
    if !profile_ready {
        blockers.push(format!(
            "ccd status cannot inspect continuity or workspace-health state because profile `{}` is not bootstrapped at {}. Run `{attach_command}` first.",
            profile,
            layout.profile_root().display(),
        ));
    }
    if !linked {
        blockers.push(format!(
            "ccd status cannot inspect continuity or workspace-health state because {} is missing. Run `{attach_command}` to bootstrap a new project overlay, or `{link_command}` to reconnect to an existing one.",
            repo_root.join(repo_marker::MARKER_FILE).display(),
        ));
    }
    if let Some(locality_id) = locality_id {
        if !registry_ready {
            blockers.push(format!(
                "ccd status found project ID `{locality_id}` (`locality_id` compatibility) is not linked in the registry; continuity and workspace-health state may refer to an unregistered project. Run `{}` first.",
                with_profile_flag(
                    &format!("ccd link --path . --project-id {locality_id}"),
                    profile,
                    explicit_profile
                )
            ));
        }
    }

    Ok(BootstrapState {
        status: if blockers.is_empty() {
            "ready"
        } else {
            "blocked"
        },
        profile_ready,
        linked,
        registry_ready,
        blockers,
    })
}

fn recommended_bootstrap_command(
    locality_id: Option<&str>,
    profile: &ProfileName,
    explicit_profile: Option<&str>,
    profile_ready: bool,
    linked: bool,
    registry_ready: bool,
) -> Option<String> {
    if !profile_ready || !linked {
        return Some(with_profile_flag(
            "ccd attach --path .",
            profile,
            explicit_profile,
        ));
    }
    if !registry_ready {
        return locality_id.map(|locality_id| {
            with_profile_flag(
                &format!("ccd link --path . --project-id {locality_id}"),
                profile,
                explicit_profile,
            )
        });
    }
    None
}

fn build_continuity_view(path: &Path, handoff: Option<&RuntimeHandoffState>) -> ContinuityView {
    let handoff = handoff.cloned().unwrap_or_default();
    let loaded = !handoff.title.is_empty()
        || !handoff.immediate_actions.is_empty()
        || !handoff.completed_state.is_empty()
        || !handoff.operational_guardrails.is_empty()
        || !handoff.key_files.is_empty()
        || !handoff.definition_of_done.is_empty();
    ContinuityView {
        path: path.display().to_string(),
        status: if loaded { "loaded" } else { "missing" },
        title: (!handoff.title.is_empty()).then_some(handoff.title.clone()),
        immediate_actions: if loaded {
            handoff
                .immediate_actions
                .iter()
                .map(|item| item.text.clone())
                .collect()
        } else {
            Vec::new()
        },
        definition_of_done: if loaded {
            handoff
                .definition_of_done
                .iter()
                .map(|item| item.text.clone())
                .collect()
        } else {
            Vec::new()
        },
    }
}

fn build_session_view(
    path: &Path,
    tracked_session: Option<&session_state::SessionStateFile>,
    tracked_activity: Option<&session_state::SessionActivityState>,
    now_epoch_s: u64,
) -> StatusSessionView {
    let Some(state) = tracked_session else {
        return StatusSessionView {
            path: path.display().to_string(),
            status: "missing",
            session_id: None,
            mode: None,
            started_at_epoch_s: None,
            last_started_at_epoch_s: None,
            start_count: None,
            session_minutes: None,
            lifecycle: session_state::SessionLifecycleProjection::missing(),
        };
    };

    let lifecycle = session_state::lifecycle_projection(state, now_epoch_s, None, tracked_activity);
    let stale = lifecycle.stale == Some(true) || state.session_id.is_none();
    let status = if stale { "stale" } else { "active" };

    StatusSessionView {
        path: path.display().to_string(),
        status,
        session_id: (!stale).then(|| state.session_id.clone()).flatten(),
        mode: Some(state.mode),
        started_at_epoch_s: Some(state.started_at_epoch_s),
        last_started_at_epoch_s: Some(state.last_started_at_epoch_s),
        start_count: Some(state.start_count),
        session_minutes: Some(session_state::session_minutes(state, now_epoch_s)),
        lifecycle,
    }
}

fn build_telemetry_view(
    layout: &StateLayout,
    locality_id: &str,
    active_session_id: Option<&str>,
    host_snapshot: Option<&host_telemetry::HostContextSnapshot>,
    continuity: &ContinuityView,
    execution_gates: &session_gates::ExecutionGatesView,
) -> Result<StatusTelemetryView> {
    let focus = telemetry_cost::continuity_target(
        execution_gates.attention_anchor.as_ref(),
        continuity.title.as_deref().unwrap_or(""),
        &continuity.immediate_actions,
    );
    let cost = telemetry_cost::build_cost_view_for_focus(
        layout,
        locality_id,
        active_session_id,
        host_snapshot,
        focus.as_ref(),
    )?;
    let Some(host_snapshot) = host_snapshot else {
        return Ok(StatusTelemetryView {
            status: "unavailable",
            host: None,
            observed_at_epoch_s: None,
            model: None,
            context_used_pct: None,
            total_tokens: None,
            context_window_tokens: None,
            compacted: None,
            cost,
        });
    };

    Ok(StatusTelemetryView {
        status: "observed",
        host: Some(host_snapshot.host),
        observed_at_epoch_s: Some(host_snapshot.observed_at_epoch_s),
        model: host_snapshot.model_name.clone(),
        context_used_pct: host_snapshot.context_used_pct,
        total_tokens: host_snapshot.total_tokens,
        context_window_tokens: host_snapshot.model_context_window,
        compacted: host_snapshot.compacted,
        cost,
    })
}

fn derive_phase(
    profile: &ProfileName,
    explicit_profile: Option<&str>,
    continuity_status: &str,
    session_status: &str,
) -> (StatusPhase, String, Option<String>) {
    match continuity_status {
        "missing" => (
            StatusPhase::StartRequired,
            "workspace-local continuity is missing; run `ccd start --activate` to load the handoff and mark session telemetry active in one step".to_owned(),
            Some(with_profile_flag(
                "ccd start --activate --path .",
                profile,
                explicit_profile,
            )),
        ),
        _ => match session_status {
            "missing" => (
                StatusPhase::ReadyForSession,
                "continuity is loaded, but no active session telemetry is present in this workspace"
                    .to_owned(),
                Some(with_profile_flag(
                    "ccd session-state start --path .",
                    profile,
                    explicit_profile,
                )),
            ),
            "stale" => (
                StatusPhase::SessionStale,
                "continuity is loaded, but session telemetry is stale or missing a durable session id".to_owned(),
                Some(with_profile_flag(
                    "ccd session-state start --path .",
                    profile,
                    explicit_profile,
                )),
            ),
            _ => (
                StatusPhase::SessionActive,
                "continuity is loaded and session telemetry is active in this clone".to_owned(),
                None,
            ),
        },
    }
}

fn build_review_view(
    profile: &ProfileName,
    explicit_profile: Option<&str>,
    git: Option<&GitState>,
    session: &StatusSessionView,
    escalation: &escalation_state::EscalationView,
    continuity: &ContinuityView,
) -> StatusReviewView {
    let radar_command = with_profile_flag("ccd handover --path .", profile, explicit_profile);

    if continuity.status == "missing" {
        return StatusReviewView {
            status: ReviewStatus::Idle,
            summary: "close-out review is not available until continuity has been loaded with `ccd start`".to_owned(),
            recommended_command: None,
        };
    }

    if escalation.blocking_count > 0 {
        return StatusReviewView {
            status: ReviewStatus::AttentionRequired,
            summary: format!(
                "{} blocking escalation(s) are active; human resolution is required before treating the session as clean",
                escalation.blocking_count
            ),
            recommended_command: Some(radar_command),
        };
    }

    if git.is_some_and(|git| !git.clean) {
        return StatusReviewView {
            status: ReviewStatus::Recommended,
            summary: "the worktree has local changes; run radar before close-out to assess the session delta".to_owned(),
            recommended_command: Some(radar_command),
        };
    }

    if session.status == "active" {
        return StatusReviewView {
            status: ReviewStatus::Available,
            summary: "session telemetry is active; run radar when you are ready to close out"
                .to_owned(),
            recommended_command: Some(radar_command),
        };
    }

    if session.status == "stale" {
        return StatusReviewView {
            status: ReviewStatus::Available,
            summary: "a stale session record exists; refresh session telemetry before relying on radar context-health timing".to_owned(),
            recommended_command: Some(radar_command),
        };
    }

    StatusReviewView {
        status: ReviewStatus::Available,
        summary: "continuity is loaded; run radar when you want a factual close-out assessment"
            .to_owned(),
        recommended_command: Some(radar_command),
    }
}

fn with_profile_flag(
    command: &str,
    profile: &ProfileName,
    explicit_profile: Option<&str>,
) -> String {
    if explicit_profile.is_none() && profile.as_str() == DEFAULT_PROFILE {
        command.to_owned()
    } else {
        format!("{command} --profile {}", profile.as_str())
    }
}

fn phase_label(phase: StatusPhase) -> &'static str {
    match phase {
        StatusPhase::BootstrapRequired => "bootstrap required",
        StatusPhase::StartRequired => "start required",
        StatusPhase::ReadyForSession => "ready for session",
        StatusPhase::SessionStale => "session stale",
        StatusPhase::SessionActive => "session active",
    }
}

fn read_handoff_state_read_only(
    layout: &StateLayout,
) -> Result<ReadOnlyState<RuntimeHandoffState>> {
    if let Some(conn) = open_state_db_read_only(layout)? {
        match read_db_handoff_state(&conn)? {
            ReadOnlyDbState::Loaded(handoff) => {
                return Ok(ReadOnlyState {
                    path: layout.state_db_path(),
                    value: handoff,
                });
            }
            ReadOnlyDbState::MissingTable => {}
        }
    }

    let path = layout.clone_runtime_state_path();
    let value = read_legacy_json::<LegacyCloneRuntimeState>(&path)?.map(|legacy| legacy.handoff);
    Ok(ReadOnlyState { path, value })
}

fn read_session_state_read_only(
    layout: &StateLayout,
) -> Result<ReadOnlyState<session_state::SessionStateFile>> {
    if let Some(conn) = open_state_db_read_only(layout)? {
        match read_db_session_state(&conn)? {
            ReadOnlyDbState::Loaded(session) => {
                return Ok(ReadOnlyState {
                    path: layout.state_db_path(),
                    value: session,
                });
            }
            ReadOnlyDbState::MissingTable => {}
        }
    }

    let path = layout.session_state_path();
    let value = read_legacy_json::<session_state::SessionStateFile>(&path)?;
    Ok(ReadOnlyState { path, value })
}

fn read_execution_gates_state_read_only(
    layout: &StateLayout,
) -> Result<Option<session_gates::ExecutionGateStateFile>> {
    let Some(conn) = open_state_db_read_only(layout)? else {
        return Ok(None);
    };
    match read_db_execution_gates(&conn)? {
        ReadOnlyDbState::Loaded(gates) => Ok(gates),
        ReadOnlyDbState::MissingTable => Ok(None),
    }
}

fn read_escalations_read_only(
    layout: &StateLayout,
) -> Result<Vec<escalation_state::EscalationEntry>> {
    if let Some(conn) = open_state_db_read_only(layout)? {
        match read_db_escalations(&conn)? {
            ReadOnlyDbState::Loaded(entries) => return Ok(entries),
            ReadOnlyDbState::MissingTable => {}
        }
    }

    Ok(
        read_legacy_json::<escalation_state::EscalationStateFile>(&layout.escalation_state_path())?
            .map(|legacy| legacy.entries)
            .unwrap_or_default(),
    )
}

fn open_state_db_read_only(layout: &StateLayout) -> Result<Option<Connection>> {
    let path = layout.state_db_path();
    if !path.exists() {
        return Ok(None);
    }

    Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY)
        .with_context(|| format!("open state.db read-only: {}", path.display()))
        .map(Some)
}

fn read_legacy_json<T>(path: &Path) -> Result<Option<T>>
where
    T: for<'de> Deserialize<'de>,
{
    let contents = match fs::read_to_string(path) {
        Ok(contents) => contents,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(error) => {
            return Err(error).with_context(|| format!("failed to read {}", path.display()));
        }
    };

    let value = serde_json::from_str(&contents)
        .with_context(|| format!("failed to parse {}", path.display()))?;
    Ok(Some(value))
}

fn read_db_handoff_state(
    conn: &Connection,
) -> Result<ReadOnlyDbState<Option<RuntimeHandoffState>>> {
    match db::handoff::read(conn) {
        Ok(value) => Ok(ReadOnlyDbState::Loaded(value)),
        Err(error) if is_missing_table_error(&error) => Ok(ReadOnlyDbState::MissingTable),
        Err(error) => Err(error),
    }
}

fn read_db_session_state(
    conn: &Connection,
) -> Result<ReadOnlyDbState<Option<session_state::SessionStateFile>>> {
    match db::session::read(conn) {
        Ok(value) => Ok(ReadOnlyDbState::Loaded(value)),
        Err(error) if is_missing_table_error(&error) => Ok(ReadOnlyDbState::MissingTable),
        Err(error) => Err(error),
    }
}

fn read_db_execution_gates(
    conn: &Connection,
) -> Result<ReadOnlyDbState<Option<session_gates::ExecutionGateStateFile>>> {
    match db::session_gates::read(conn) {
        Ok(value) => Ok(ReadOnlyDbState::Loaded(value)),
        Err(error) if is_missing_table_error(&error) => Ok(ReadOnlyDbState::MissingTable),
        Err(error) => Err(error),
    }
}

fn read_db_escalations(
    conn: &Connection,
) -> Result<ReadOnlyDbState<Vec<escalation_state::EscalationEntry>>> {
    match db::escalation::list(conn) {
        Ok(value) => Ok(ReadOnlyDbState::Loaded(value)),
        Err(error) if is_missing_table_error(&error) => Ok(ReadOnlyDbState::MissingTable),
        Err(error) => Err(error),
    }
}

fn is_missing_table_error(error: &anyhow::Error) -> bool {
    error
        .downcast_ref::<rusqlite::Error>()
        .and_then(|db_error| match db_error {
            rusqlite::Error::SqliteFailure(_, Some(message)) => {
                Some(message.contains("no such table"))
            }
            _ => None,
        })
        .unwrap_or(false)
}
