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

use anyhow::{Context, Result};
use serde::Serialize;
use sha2::{Digest, Sha256};

use crate::paths::state::StateLayout;
use crate::state::{
    escalation as escalation_state, machine_registry as pod_identity, session as session_state,
};

#[derive(Debug, Clone)]
pub(crate) struct PolicySource {
    pub(crate) kind: &'static str,
    pub(crate) path: PathBuf,
    pub(crate) status: &'static str,
    pub(crate) content: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PolicyDecision {
    Allow,
    ApprovalRequired,
    EscalationRequired,
    Deny,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PolicyActionFamily {
    ReadOnly,
    LocalExecution,
    LocalIsolation,
    LocalFinalize,
    PublishExternal,
    SharedQueueMutation,
    DurableMemoryMutation,
    RepairDestructive,
    ProtectedSurfaceMutation,
}

#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum AutonomousPolicyPosture {
    GuardedLocalExecution,
}

#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum InteractiveCompatibilityMode {
    HumanSupervised,
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct PolicyProjectionView {
    pub(crate) status: &'static str,
    pub(crate) source_fingerprint: String,
    pub(crate) authored_policy: AuthoredPolicyView,
    pub(crate) interactive_compatibility_mode: InteractiveCompatibilityMode,
    pub(crate) autonomous_default_posture: AutonomousPolicyPosture,
    pub(crate) fail_closed: bool,
    pub(crate) action_families: Vec<ActionFamilyDecisionView>,
    pub(crate) runtime_constraints: PolicyRuntimeConstraintsView,
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct AuthoredPolicyView {
    pub(crate) status: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) effective_content_sha256: Option<String>,
    pub(crate) parts: Vec<PolicySourceView>,
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct PolicySourceView {
    pub(crate) kind: &'static str,
    pub(crate) path: String,
    pub(crate) status: &'static str,
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct ActionFamilyDecisionView {
    pub(crate) family: PolicyActionFamily,
    pub(crate) default_decision: PolicyDecision,
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct PolicyRuntimeConstraintsView {
    pub(crate) blocking_escalation_active: bool,
    pub(crate) blocking_escalation_count: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) session_lifecycle: Option<session_state::SessionLifecycle>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) session_owner_kind: Option<session_state::SessionOwnerKind>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) session_mode: Option<session_state::SessionMode>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) session_stale: Option<bool>,
    pub(crate) protected_writes_require_current_owner: bool,
    pub(crate) stale_session_blocks_protected_writes: bool,
}

#[derive(Debug, Clone, Serialize)]
pub(crate) struct PolicyEvaluationView {
    pub(crate) decision: PolicyDecision,
    pub(crate) reason_code: &'static str,
    pub(crate) summary: String,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct PolicySessionContext {
    pub(crate) mode: Option<session_state::SessionMode>,
    pub(crate) lifecycle: Option<session_state::SessionLifecycle>,
    pub(crate) owner_kind: Option<session_state::SessionOwnerKind>,
    pub(crate) stale: Option<bool>,
}

const DEFAULT_AUTONOMOUS_POLICY: &[(PolicyActionFamily, PolicyDecision)] = &[
    (PolicyActionFamily::ReadOnly, PolicyDecision::Allow),
    (PolicyActionFamily::LocalExecution, PolicyDecision::Allow),
    (PolicyActionFamily::LocalIsolation, PolicyDecision::Allow),
    (
        PolicyActionFamily::LocalFinalize,
        PolicyDecision::ApprovalRequired,
    ),
    (
        PolicyActionFamily::PublishExternal,
        PolicyDecision::ApprovalRequired,
    ),
    (
        PolicyActionFamily::SharedQueueMutation,
        PolicyDecision::ApprovalRequired,
    ),
    (
        PolicyActionFamily::DurableMemoryMutation,
        PolicyDecision::ApprovalRequired,
    ),
    (PolicyActionFamily::RepairDestructive, PolicyDecision::Deny),
    (
        PolicyActionFamily::ProtectedSurfaceMutation,
        PolicyDecision::ApprovalRequired,
    ),
];

impl PolicySessionContext {
    pub(crate) fn missing() -> Self {
        Self {
            mode: None,
            lifecycle: None,
            owner_kind: None,
            stale: None,
        }
    }

    pub(crate) fn from_start_view(
        mode: Option<session_state::SessionMode>,
        lifecycle: &session_state::SessionLifecycleProjection,
    ) -> Self {
        Self {
            mode,
            lifecycle: lifecycle.lifecycle,
            owner_kind: lifecycle.owner_kind,
            stale: lifecycle.stale,
        }
    }
}

pub(crate) fn skipped_view() -> PolicyProjectionView {
    PolicyProjectionView {
        status: "skipped",
        source_fingerprint: String::new(),
        authored_policy: AuthoredPolicyView {
            status: "skipped",
            effective_content_sha256: None,
            parts: Vec::new(),
        },
        interactive_compatibility_mode: InteractiveCompatibilityMode::HumanSupervised,
        autonomous_default_posture: AutonomousPolicyPosture::GuardedLocalExecution,
        fail_closed: true,
        action_families: default_action_families(),
        runtime_constraints: PolicyRuntimeConstraintsView {
            blocking_escalation_active: false,
            blocking_escalation_count: 0,
            session_lifecycle: None,
            session_owner_kind: None,
            session_mode: None,
            session_stale: None,
            protected_writes_require_current_owner: true,
            stale_session_blocks_protected_writes: true,
        },
    }
}

pub(crate) fn read_policy_sources(
    layout: &StateLayout,
    locality_id: &str,
    active_pod_identity: Option<&pod_identity::LoadedPodIdentity>,
) -> Result<Vec<PolicySource>> {
    let mut sources = Vec::new();

    if let Some(identity) = active_pod_identity {
        sources.push(read_policy_source("pod_policy", &identity.policy_path)?);
    }

    sources.push(read_policy_source(
        "profile_policy",
        &layout.profile_policy_path(),
    )?);
    sources.push(read_policy_source(
        "locality_policy",
        &layout.locality_policy_path(locality_id)?,
    )?);

    Ok(sources)
}

pub(crate) fn load_session_context(layout: &StateLayout) -> Result<PolicySessionContext> {
    let Some(state) = session_state::load_for_layout(layout)? else {
        return Ok(PolicySessionContext::missing());
    };

    let now = session_state::now_epoch_s()?;
    let lifecycle = session_state::lifecycle_projection(&state, now, None, None);
    Ok(PolicySessionContext {
        mode: Some(state.mode),
        lifecycle: lifecycle.lifecycle,
        owner_kind: lifecycle.owner_kind,
        stale: lifecycle.stale,
    })
}

pub(crate) fn build_view(
    sources: &[PolicySource],
    session: PolicySessionContext,
    escalation: &escalation_state::EscalationView,
) -> PolicyProjectionView {
    let authored_policy = authored_policy_view(sources);

    PolicyProjectionView {
        status: "derived",
        source_fingerprint: fingerprint_sources(sources),
        authored_policy,
        interactive_compatibility_mode: InteractiveCompatibilityMode::HumanSupervised,
        autonomous_default_posture: AutonomousPolicyPosture::GuardedLocalExecution,
        fail_closed: true,
        action_families: default_action_families(),
        runtime_constraints: PolicyRuntimeConstraintsView {
            blocking_escalation_active: escalation.blocking_count > 0,
            blocking_escalation_count: escalation.blocking_count,
            session_lifecycle: session.lifecycle,
            session_owner_kind: session.owner_kind,
            session_mode: session.mode,
            session_stale: session.stale,
            protected_writes_require_current_owner: true,
            stale_session_blocks_protected_writes: true,
        },
    }
}

pub(crate) fn evaluate_action_family(
    projection: &PolicyProjectionView,
    family: PolicyActionFamily,
) -> PolicyEvaluationView {
    let constraints = &projection.runtime_constraints;

    if matches!(family, PolicyActionFamily::ReadOnly) {
        return PolicyEvaluationView {
            decision: PolicyDecision::Allow,
            reason_code: "read_only_allowed",
            summary: "Read-only inspection stays allowed under the default policy posture."
                .to_owned(),
        };
    }

    if constraints.blocking_escalation_active {
        return PolicyEvaluationView {
            decision: PolicyDecision::EscalationRequired,
            reason_code: "blocking_escalation_active",
            summary: format!(
                "{} blocking escalation(s) are active; resolve them before autonomous {}.",
                constraints.blocking_escalation_count,
                family.label()
            ),
        };
    }

    if matches!(
        constraints.session_lifecycle,
        Some(session_state::SessionLifecycle::Autonomous)
    ) && matches!(constraints.session_stale, Some(true))
    {
        return PolicyEvaluationView {
            decision: PolicyDecision::EscalationRequired,
            reason_code: "stale_autonomous_session",
            summary: format!(
                "The active autonomous session lease is stale; heartbeat or takeover before {}.",
                family.label()
            ),
        };
    }

    let default_decision = default_decision_for_family(family);
    PolicyEvaluationView {
        decision: default_decision,
        reason_code: default_reason_code(family, default_decision),
        summary: default_summary(family, default_decision).to_owned(),
    }
}

fn read_policy_source(kind: &'static str, path: &Path) -> Result<PolicySource> {
    match fs::read_to_string(path) {
        Ok(contents) => Ok(PolicySource {
            kind,
            path: path.to_path_buf(),
            status: "loaded",
            content: Some(contents),
        }),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(PolicySource {
            kind,
            path: path.to_path_buf(),
            status: "missing",
            content: None,
        }),
        Err(error) => Err(error).with_context(|| format!("failed to read {}", path.display())),
    }
}

fn authored_policy_view(sources: &[PolicySource]) -> AuthoredPolicyView {
    let content = joined_content(sources);
    let status = if content.is_empty() {
        if sources.iter().any(|source| source.status == "loaded") {
            "empty"
        } else {
            "missing"
        }
    } else {
        "loaded"
    };

    AuthoredPolicyView {
        status,
        effective_content_sha256: (!content.is_empty()).then(|| sha256_string(&content)),
        parts: sources
            .iter()
            .map(|source| PolicySourceView {
                kind: source.kind,
                path: source.path.display().to_string(),
                status: source.status,
            })
            .collect(),
    }
}

fn joined_content(sources: &[PolicySource]) -> String {
    sources
        .iter()
        .filter_map(|source| source.content.as_deref())
        .filter(|content| !content.is_empty())
        .map(ToOwned::to_owned)
        .collect::<Vec<_>>()
        .join("\n\n")
}

fn fingerprint_sources(sources: &[PolicySource]) -> String {
    let mut hasher = Sha256::new();
    for source in sources {
        hasher.update(source.kind.as_bytes());
        hasher.update([0]);
        hasher.update(source.path.as_os_str().to_string_lossy().as_bytes());
        hasher.update([0]);
        hasher.update(source.status.as_bytes());
        hasher.update([0]);
        if let Some(content) = &source.content {
            hasher.update(content.as_bytes());
        }
        hasher.update([0xff]);
    }
    format!("{:x}", hasher.finalize())
}

fn default_action_families() -> Vec<ActionFamilyDecisionView> {
    DEFAULT_AUTONOMOUS_POLICY
        .iter()
        .map(|(family, default_decision)| ActionFamilyDecisionView {
            family: *family,
            default_decision: *default_decision,
        })
        .collect()
}

fn default_decision_for_family(family: PolicyActionFamily) -> PolicyDecision {
    DEFAULT_AUTONOMOUS_POLICY
        .iter()
        .find(|(candidate, _)| *candidate == family)
        .map(|(_, decision)| *decision)
        .unwrap_or(PolicyDecision::Deny)
}

fn default_reason_code(family: PolicyActionFamily, decision: PolicyDecision) -> &'static str {
    match (family, decision) {
        (PolicyActionFamily::ReadOnly, PolicyDecision::Allow) => "read_only_allowed",
        (PolicyActionFamily::LocalExecution, PolicyDecision::Allow) => "local_execution_allowed",
        (PolicyActionFamily::LocalIsolation, PolicyDecision::Allow) => "local_isolation_allowed",
        (PolicyActionFamily::LocalFinalize, PolicyDecision::ApprovalRequired) => {
            "local_finalize_requires_approval"
        }
        (PolicyActionFamily::PublishExternal, PolicyDecision::ApprovalRequired) => {
            "publish_requires_approval"
        }
        (PolicyActionFamily::SharedQueueMutation, PolicyDecision::ApprovalRequired) => {
            "shared_queue_requires_approval"
        }
        (PolicyActionFamily::DurableMemoryMutation, PolicyDecision::ApprovalRequired) => {
            "durable_memory_requires_approval"
        }
        (PolicyActionFamily::RepairDestructive, PolicyDecision::Deny) => {
            "repair_destructive_denied"
        }
        (PolicyActionFamily::ProtectedSurfaceMutation, PolicyDecision::ApprovalRequired) => {
            "protected_surface_requires_approval"
        }
        (_, PolicyDecision::Deny) => "action_denied",
        (_, PolicyDecision::ApprovalRequired) => "approval_required",
        (_, PolicyDecision::EscalationRequired) => "escalation_required",
        (_, PolicyDecision::Allow) => "allowed",
    }
}

fn default_summary(family: PolicyActionFamily, decision: PolicyDecision) -> &'static str {
    match (family, decision) {
        (PolicyActionFamily::ReadOnly, PolicyDecision::Allow) => {
            "Read-only inspection stays allowed."
        }
        (PolicyActionFamily::LocalExecution, PolicyDecision::Allow) => {
            "In-scope local execution is allowed under the default guarded posture."
        }
        (PolicyActionFamily::LocalIsolation, PolicyDecision::Allow) => {
            "Local isolation work such as branch or workspace setup is allowed by default."
        }
        (PolicyActionFamily::LocalFinalize, PolicyDecision::ApprovalRequired) => {
            "Local finalization still needs explicit approval by default."
        }
        (PolicyActionFamily::PublishExternal, PolicyDecision::ApprovalRequired) => {
            "External publication stays behind explicit approval by default."
        }
        (PolicyActionFamily::SharedQueueMutation, PolicyDecision::ApprovalRequired) => {
            "Shared queue mutations need explicit approval by default."
        }
        (PolicyActionFamily::DurableMemoryMutation, PolicyDecision::ApprovalRequired) => {
            "Durable memory mutation needs explicit approval by default."
        }
        (PolicyActionFamily::RepairDestructive, PolicyDecision::Deny) => {
            "Repair and destructive actions are denied in the default autonomous posture."
        }
        (PolicyActionFamily::ProtectedSurfaceMutation, PolicyDecision::ApprovalRequired) => {
            "Protected-surface mutation never auto-runs under the default posture."
        }
        (_, PolicyDecision::Deny) => "This action family is denied.",
        (_, PolicyDecision::ApprovalRequired) => "This action family needs explicit approval.",
        (_, PolicyDecision::EscalationRequired) => {
            "This action family is blocked pending escalation."
        }
        (_, PolicyDecision::Allow) => "This action family is allowed.",
    }
}

fn sha256_string(content: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(content.as_bytes());
    format!("{:x}", hasher.finalize())
}

impl PolicyActionFamily {
    pub(crate) fn label(self) -> &'static str {
        match self {
            Self::ReadOnly => "read-only inspection",
            Self::LocalExecution => "local execution",
            Self::LocalIsolation => "local isolation work",
            Self::LocalFinalize => "local finalization",
            Self::PublishExternal => "external publication",
            Self::SharedQueueMutation => "shared queue mutation",
            Self::DurableMemoryMutation => "durable memory mutation",
            Self::RepairDestructive => "repair or destructive work",
            Self::ProtectedSurfaceMutation => "protected-surface mutation",
        }
    }
}
