//! State machine for approval workflow transitions.
//!
//! Handles the state transitions for approval requests including
//! approve, reject, escalate, and timeout handling.

use chrono::{Duration, Utc};
use tracing::{debug, info, warn};
use uuid::Uuid;

use super::audit::{AuditEvent, AuditEventType, AuditTrail};
use super::store::GovernanceStore;
use super::types::{ApprovalDecision, ApprovalRequest, ApprovalStatus, Decision};
use super::workflow::ApprovalWorkflow;
use crate::config::GovernanceConfig;
use crate::evidence::EvidenceLevel;
use crate::learning::LearnedPattern;
use crate::AphoriaError;

/// State machine for governance approval workflows.
pub struct GovernanceStateMachine {
    store: GovernanceStore,
    /// Configuration for governance workflows.
    pub config: GovernanceConfig,
}

impl GovernanceStateMachine {
    /// Create a new state machine with the given store and config.
    pub fn new(store: GovernanceStore, config: GovernanceConfig) -> Self {
        Self { store, config }
    }

    /// Create a state machine with the default store.
    pub fn open_default(config: GovernanceConfig) -> Result<Self, AphoriaError> {
        let store = GovernanceStore::open_default()?;
        Ok(Self::new(store, config))
    }

    /// Get the appropriate workflow for a pattern.
    pub fn get_workflow_for_pattern(&self, pattern: &LearnedPattern) -> Option<ApprovalWorkflow> {
        let evidence_level = pattern.evidence.effective_level();

        // Find a matching workflow based on evidence level
        for workflow in &self.config.workflows {
            if workflow.applies_to_evidence(evidence_level) {
                return Some(workflow.clone());
            }
        }

        // Fall back to default workflow
        self.config
            .default_workflow
            .as_ref()
            .and_then(|name| self.config.workflows.iter().find(|w| w.name == *name).cloned())
    }

    /// Create a new approval request for a pattern.
    pub fn create_request(
        &self,
        pattern: &LearnedPattern,
        workflow: &ApprovalWorkflow,
        created_by: &str,
    ) -> Result<ApprovalRequest, AphoriaError> {
        // Get first stage
        let first_stage = workflow
            .first_stage()
            .ok_or_else(|| AphoriaError::Governance("Workflow has no stages".to_string()))?;

        // Create the request
        let mut request = ApprovalRequest::new(
            pattern.id,
            &pattern.claim_template.predicate,
            &workflow.name,
            &first_stage.name,
            created_by,
        );

        // Set evidence summary
        let evidence_summary = format!(
            "Evidence level: {} ({} sources)",
            pattern.evidence.effective_level(),
            pattern.evidence.source_count()
        );
        request = request.with_evidence_summary(evidence_summary);

        // Set deadline if stage has timeout
        if let Some(hours) = first_stage.timeout_hours {
            let deadline = Utc::now() + Duration::hours(i64::from(hours));
            request = request.with_deadline(deadline);
        }

        // Check for auto-approval at first stage
        if first_stage.should_auto_approve(pattern.evidence.effective_level()) {
            debug!(
                pattern_id = %pattern.id,
                stage = %first_stage.name,
                "Auto-approving first stage based on evidence level"
            );

            // Add auto-approval decision
            let decision = ApprovalDecision::new(
                request.id,
                &first_stage.name,
                Decision::Approved,
                "system",
                Some("Auto-approved based on evidence level".to_string()),
            );
            request.add_decision(decision.clone());
            self.store.log_decision(&decision)?;

            // Advance or complete
            self.advance_after_approval(
                &mut request,
                workflow,
                pattern.evidence.effective_level(),
            )?;
        }

        // Save the request
        self.store.save_request(&request)?;

        // Log audit event
        if let Ok(audit) = AuditTrail::open_default() {
            let _ = audit.log_event(AuditEvent::new(
                AuditEventType::RequestCreated,
                request.id,
                pattern.id,
                created_by.to_string(),
                serde_json::json!({
                    "workflow": workflow.name,
                    "first_stage": first_stage.name,
                }),
            ));
        }

        info!(
            request_id = %request.id,
            pattern_id = %pattern.id,
            workflow = %workflow.name,
            "Created approval request"
        );

        Ok(request)
    }

    /// Approve the current stage of a request.
    pub fn approve(
        &self,
        request_id: Uuid,
        approver: &str,
        comment: Option<String>,
    ) -> Result<ApprovalRequest, AphoriaError> {
        let mut request = self
            .store
            .get_request(&request_id)?
            .ok_or_else(|| AphoriaError::Governance(format!("Request {} not found", request_id)))?;

        // Check if request is pending
        let stage_name = match &request.status {
            ApprovalStatus::Pending { stage } => stage.clone(),
            status => {
                return Err(AphoriaError::Governance(format!(
                    "Request is not pending: {}",
                    status
                )));
            }
        };

        // Get the workflow
        let workflow = self
            .config
            .workflows
            .iter()
            .find(|w| w.name == request.workflow_name)
            .ok_or_else(|| {
                AphoriaError::Governance(format!("Workflow '{}' not found", request.workflow_name))
            })?;

        // Get the current stage
        let (_, stage) = workflow
            .get_stage_by_name(&stage_name)
            .ok_or_else(|| AphoriaError::Governance(format!("Stage '{}' not found", stage_name)))?;

        // Check if approver is authorized
        if !stage.is_authorized(approver) {
            return Err(AphoriaError::Governance(format!(
                "User '{}' is not authorized to approve stage '{}'",
                approver, stage_name
            )));
        }

        // Create and record the decision
        let decision =
            ApprovalDecision::new(request_id, &stage_name, Decision::Approved, approver, comment);
        request.add_decision(decision.clone());
        self.store.log_decision(&decision)?;

        // Check if we have enough approvals
        let approval_count = request.current_stage_approval_count();
        if approval_count >= stage.min_approvals {
            debug!(
                request_id = %request_id,
                stage = %stage_name,
                approvals = approval_count,
                required = stage.min_approvals,
                "Stage approval threshold reached"
            );

            // Determine evidence level for auto-approve checks
            let evidence_level = EvidenceLevel::default(); // Could be passed in
            self.advance_after_approval(&mut request, workflow, evidence_level)?;
        }

        // Save the updated request
        self.store.save_request(&request)?;

        // Log audit event
        if let Ok(audit) = AuditTrail::open_default() {
            let _ = audit.log_event(AuditEvent::new(
                AuditEventType::StageApproved,
                request_id,
                request.pattern_id,
                approver.to_string(),
                serde_json::json!({
                    "stage": stage_name,
                    "approval_count": approval_count,
                }),
            ));
        }

        info!(
            request_id = %request_id,
            stage = %stage_name,
            approver = %approver,
            "Stage approved"
        );

        Ok(request)
    }

    /// Advance the request after stage approval.
    fn advance_after_approval(
        &self,
        request: &mut ApprovalRequest,
        workflow: &ApprovalWorkflow,
        evidence_level: EvidenceLevel,
    ) -> Result<(), AphoriaError> {
        let current_index = request.current_stage_index;

        if workflow.is_last_stage(current_index) {
            // Final stage - mark as approved
            request.mark_approved();
            info!(request_id = %request.id, "Request fully approved");
        } else {
            // Advance to next stage
            if let Some(next_stage) = workflow.next_stage(current_index) {
                request.advance_to_stage(&next_stage.name);

                // Set deadline for next stage
                if let Some(hours) = next_stage.timeout_hours {
                    let deadline = Utc::now() + Duration::hours(i64::from(hours));
                    request.stage_deadline = Some(deadline);
                }

                // Check for auto-approval at next stage
                if next_stage.should_auto_approve(evidence_level) {
                    debug!(
                        request_id = %request.id,
                        stage = %next_stage.name,
                        "Auto-approving stage based on evidence level"
                    );

                    let decision = ApprovalDecision::new(
                        request.id,
                        &next_stage.name,
                        Decision::Approved,
                        "system",
                        Some("Auto-approved based on evidence level".to_string()),
                    );
                    request.add_decision(decision.clone());
                    self.store.log_decision(&decision)?;

                    // Recurse to handle next stage
                    request.current_stage_index += 1;
                    self.advance_after_approval(request, workflow, evidence_level)?;
                }
            }
        }

        Ok(())
    }

    /// Reject a request at the current stage.
    pub fn reject(
        &self,
        request_id: Uuid,
        approver: &str,
        reason: String,
    ) -> Result<ApprovalRequest, AphoriaError> {
        let mut request = self
            .store
            .get_request(&request_id)?
            .ok_or_else(|| AphoriaError::Governance(format!("Request {} not found", request_id)))?;

        let stage_name = match &request.status {
            ApprovalStatus::Pending { stage } => stage.clone(),
            status => {
                return Err(AphoriaError::Governance(format!(
                    "Request is not pending: {}",
                    status
                )));
            }
        };

        // Create and record the decision
        let decision = ApprovalDecision::new(
            request_id,
            &stage_name,
            Decision::Rejected,
            approver,
            Some(reason.clone()),
        );
        request.add_decision(decision.clone());
        self.store.log_decision(&decision)?;

        // Mark as rejected
        request.mark_rejected(&stage_name, &reason);
        self.store.save_request(&request)?;

        // Log audit event
        if let Ok(audit) = AuditTrail::open_default() {
            let _ = audit.log_event(AuditEvent::new(
                AuditEventType::StageRejected,
                request_id,
                request.pattern_id,
                approver.to_string(),
                serde_json::json!({
                    "stage": stage_name,
                    "reason": reason,
                }),
            ));
        }

        info!(
            request_id = %request_id,
            stage = %stage_name,
            approver = %approver,
            reason = %reason,
            "Request rejected"
        );

        Ok(request)
    }

    /// Escalate a request to the next stage.
    pub fn escalate(
        &self,
        request_id: Uuid,
        escalator: &str,
    ) -> Result<ApprovalRequest, AphoriaError> {
        let mut request = self
            .store
            .get_request(&request_id)?
            .ok_or_else(|| AphoriaError::Governance(format!("Request {} not found", request_id)))?;

        let stage_name = match &request.status {
            ApprovalStatus::Pending { stage } => stage.clone(),
            status => {
                return Err(AphoriaError::Governance(format!(
                    "Request is not pending: {}",
                    status
                )));
            }
        };

        // Get the workflow and current stage
        let workflow = self
            .config
            .workflows
            .iter()
            .find(|w| w.name == request.workflow_name)
            .ok_or_else(|| {
                AphoriaError::Governance(format!("Workflow '{}' not found", request.workflow_name))
            })?;

        let (_stage_index, stage) = workflow
            .get_stage_by_name(&stage_name)
            .ok_or_else(|| AphoriaError::Governance(format!("Stage '{}' not found", stage_name)))?;

        // Check if escalation is configured
        let escalate_to = stage.escalate_to.as_ref().ok_or_else(|| {
            AphoriaError::Governance(format!("Stage '{}' has no escalation target", stage_name))
        })?;

        // Get the escalation target stage
        let (target_index, target_stage) =
            workflow.get_stage_by_name(escalate_to).ok_or_else(|| {
                AphoriaError::Governance(format!("Escalation target '{}' not found", escalate_to))
            })?;

        // Perform escalation
        request.mark_escalated(&stage_name, escalate_to);
        request.current_stage_index = target_index;

        // Set deadline for target stage
        if let Some(hours) = target_stage.timeout_hours {
            let deadline = Utc::now() + Duration::hours(i64::from(hours));
            request.stage_deadline = Some(deadline);
        }

        self.store.save_request(&request)?;

        // Log audit event
        if let Ok(audit) = AuditTrail::open_default() {
            let _ = audit.log_event(AuditEvent::new(
                AuditEventType::Escalated,
                request_id,
                request.pattern_id,
                escalator.to_string(),
                serde_json::json!({
                    "from_stage": stage_name,
                    "to_stage": escalate_to,
                }),
            ));
        }

        info!(
            request_id = %request_id,
            from = %stage_name,
            to = %escalate_to,
            "Request escalated"
        );

        Ok(request)
    }

    /// Check for timed-out requests and handle them.
    ///
    /// Returns list of requests that were processed.
    pub fn check_timeouts(&self) -> Result<Vec<ApprovalRequest>, AphoriaError> {
        let expired = self.store.get_expired_requests()?;
        let mut processed = Vec::new();

        for mut request in expired {
            let stage_name = match &request.status {
                ApprovalStatus::Pending { stage } => stage.clone(),
                _ => continue,
            };

            // Get the workflow and stage
            let workflow =
                match self.config.workflows.iter().find(|w| w.name == request.workflow_name) {
                    Some(w) => w,
                    None => {
                        warn!(
                            request_id = %request.id,
                            workflow = %request.workflow_name,
                            "Workflow not found for expired request"
                        );
                        continue;
                    }
                };

            let stage = match workflow.get_stage_by_name(&stage_name) {
                Some((_, s)) => s,
                None => {
                    warn!(
                        request_id = %request.id,
                        stage = %stage_name,
                        "Stage not found for expired request"
                    );
                    continue;
                }
            };

            // Handle timeout: escalate or expire
            if let Some(ref escalate_to) = stage.escalate_to {
                // Escalate to next stage
                if let Some((target_index, target_stage)) = workflow.get_stage_by_name(escalate_to)
                {
                    request.mark_escalated(&stage_name, escalate_to);
                    request.current_stage_index = target_index;

                    if let Some(hours) = target_stage.timeout_hours {
                        let deadline = Utc::now() + Duration::hours(i64::from(hours));
                        request.stage_deadline = Some(deadline);
                    }

                    info!(
                        request_id = %request.id,
                        from = %stage_name,
                        to = %escalate_to,
                        "Request escalated due to timeout"
                    );
                }
            } else {
                // No escalation target - expire the request
                request.mark_expired();

                info!(
                    request_id = %request.id,
                    stage = %stage_name,
                    "Request expired due to timeout"
                );

                // Log audit event
                if let Ok(audit) = AuditTrail::open_default() {
                    let _ = audit.log_event(AuditEvent::new(
                        AuditEventType::Expired,
                        request.id,
                        request.pattern_id,
                        "system".to_string(),
                        serde_json::json!({
                            "stage": stage_name,
                        }),
                    ));
                }
            }

            self.store.save_request(&request)?;
            processed.push(request);
        }

        Ok(processed)
    }

    /// Get a request by ID.
    pub fn get_request(&self, id: &Uuid) -> Result<Option<ApprovalRequest>, AphoriaError> {
        self.store.get_request(id)
    }

    /// Get a request by pattern ID.
    pub fn get_request_by_pattern(
        &self,
        pattern_id: &Uuid,
    ) -> Result<Option<ApprovalRequest>, AphoriaError> {
        self.store.get_request_by_pattern(pattern_id)
    }

    /// List all pending requests.
    pub fn list_pending(&self) -> Result<Vec<ApprovalRequest>, AphoriaError> {
        self.store.list_pending()
    }

    /// List all requests.
    pub fn list_all(&self) -> Result<Vec<ApprovalRequest>, AphoriaError> {
        self.store.list_all()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::GovernanceConfig;
    use crate::governance::workflow::{ApprovalStage, ApprovalWorkflow};
    use crate::learning::{ClaimTemplate, LearnedPattern, ValueType};
    use crate::types::Language;
    use tempfile::TempDir;

    fn create_test_config() -> GovernanceConfig {
        GovernanceConfig {
            enabled: true,
            governance_dir: None,
            default_workflow: Some("test_workflow".to_string()),
            workflows: vec![ApprovalWorkflow::new("test_workflow", "Test workflow")
                .add_stage(ApprovalStage::new("stage1", "Stage 1"))
                .add_stage(ApprovalStage::new("stage2", "Stage 2"))],
            check_timeouts_on_access: false,
        }
    }

    fn create_test_pattern() -> LearnedPattern {
        LearnedPattern::new(
            "test code",
            "test pattern",
            ClaimTemplate::new("test/subject", "predicate", ValueType::Text, "description"),
            Language::Rust,
            "project_hash",
            0.9,
        )
    }

    fn create_test_state_machine(temp: &TempDir) -> GovernanceStateMachine {
        let store = GovernanceStore::new(temp.path()).expect("create store");
        let config = create_test_config();
        GovernanceStateMachine::new(store, config)
    }

    #[test]
    fn test_create_request() {
        let temp = TempDir::new().expect("temp dir");
        let sm = create_test_state_machine(&temp);
        let pattern = create_test_pattern();
        let workflow = sm.config.workflows.first().unwrap();

        let request = sm.create_request(&pattern, workflow, "test_user").expect("create");

        assert_eq!(request.pattern_id, pattern.id);
        assert_eq!(request.workflow_name, "test_workflow");
        assert!(request.status.is_pending());
        assert_eq!(request.status.current_stage(), Some("stage1"));
    }

    #[test]
    fn test_approve_stage() {
        let temp = TempDir::new().expect("temp dir");
        let sm = create_test_state_machine(&temp);
        let pattern = create_test_pattern();
        let workflow = sm.config.workflows.first().unwrap();

        let request = sm.create_request(&pattern, workflow, "test_user").expect("create");
        let updated =
            sm.approve(request.id, "approver", Some("LGTM".to_string())).expect("approve");

        // Should advance to stage2
        assert!(updated.status.is_pending());
        assert_eq!(updated.status.current_stage(), Some("stage2"));
        assert_eq!(updated.decisions.len(), 1);
    }

    #[test]
    fn test_full_approval_workflow() {
        let temp = TempDir::new().expect("temp dir");
        let sm = create_test_state_machine(&temp);
        let pattern = create_test_pattern();
        let workflow = sm.config.workflows.first().unwrap();

        let request = sm.create_request(&pattern, workflow, "test_user").expect("create");

        // Approve stage 1
        let updated = sm.approve(request.id, "approver1", None).expect("approve 1");
        assert_eq!(updated.status.current_stage(), Some("stage2"));

        // Approve stage 2
        let final_req = sm.approve(request.id, "approver2", None).expect("approve 2");
        assert!(final_req.status.is_approved());
    }

    #[test]
    fn test_reject_request() {
        let temp = TempDir::new().expect("temp dir");
        let sm = create_test_state_machine(&temp);
        let pattern = create_test_pattern();
        let workflow = sm.config.workflows.first().unwrap();

        let request = sm.create_request(&pattern, workflow, "test_user").expect("create");
        let rejected = sm.reject(request.id, "reviewer", "Too broad".to_string()).expect("reject");

        assert!(rejected.status.is_rejected());
        if let ApprovalStatus::Rejected { stage, reason } = &rejected.status {
            assert_eq!(stage, "stage1");
            assert_eq!(reason, "Too broad");
        }
    }

    #[test]
    fn test_list_pending() {
        let temp = TempDir::new().expect("temp dir");
        let sm = create_test_state_machine(&temp);
        let pattern = create_test_pattern();
        let workflow = sm.config.workflows.first().unwrap();

        sm.create_request(&pattern, workflow, "test_user").expect("create");

        let pending = sm.list_pending().expect("list");
        assert_eq!(pending.len(), 1);
    }
}
