use crate::types::*;
use chrono::{Duration, Utc};
use std::collections::HashMap;
use std::sync::Mutex;
use uuid::Uuid;

/// In-memory approval store and workflow engine. Every mutation appends to a
/// per-request immutable audit trail. Sample data is fictitious.
pub struct ApprovalStore {
    requests: Mutex<HashMap<String, ApprovalRequest>>,
    templates: Mutex<HashMap<String, WorkflowTemplate>>,
    seq: Mutex<u64>,
}

impl Default for ApprovalStore {
    fn default() -> Self {
        Self::new()
    }
}

impl ApprovalStore {
    pub fn new() -> Self {
        let s = Self {
            requests: Mutex::new(HashMap::new()),
            templates: Mutex::new(HashMap::new()),
            seq: Mutex::new(1000),
        };
        s.seed_templates();
        s.seed_requests();
        s
    }

    fn next_id(&self, prefix: &str) -> String {
        let mut n = self.seq.lock().unwrap();
        *n += 1;
        format!("{prefix}-{}", *n)
    }

    fn audit(req: &mut ApprovalRequest, actor: &str, action: &str, detail: impl Into<String>) {
        req.audit_trail.push(AuditEntry {
            at: Utc::now(),
            actor: actor.to_string(),
            action: action.to_string(),
            detail: detail.into(),
        });
        req.updated_at = Utc::now();
    }

    // ── templates ────────────────────────────────────────────────────────

    pub fn list_templates(&self, domain: Option<Domain>) -> Vec<WorkflowTemplate> {
        let mut v: Vec<WorkflowTemplate> = self
            .templates
            .lock()
            .unwrap()
            .values()
            .filter(|t| domain.is_none_or(|d| t.domain == d))
            .cloned()
            .collect();
        v.sort_by(|a, b| a.id.cmp(&b.id));
        v
    }

    pub fn get_template(&self, id: &str) -> Option<WorkflowTemplate> {
        self.templates.lock().unwrap().get(id).cloned()
    }

    /// Find a default template for a request type.
    fn template_for(&self, rt: RequestType) -> Option<WorkflowTemplate> {
        self.templates.lock().unwrap().values().find(|t| t.request_type == rt).cloned()
    }

    pub fn create_template(&self, name: String, domain: Domain, request_type: RequestType, stages: Vec<StageDef>) -> WorkflowTemplate {
        let t = WorkflowTemplate {
            id: self.next_id("WF"),
            name,
            domain,
            request_type,
            stages,
            created_at: Utc::now(),
        };
        self.templates.lock().unwrap().insert(t.id.clone(), t.clone());
        t
    }

    // ── request creation ──────────────────────────────────────────────────

    fn instantiate_stages(defs: &[StageDef]) -> Vec<Stage> {
        defs.iter()
            .enumerate()
            .map(|(i, d)| Stage {
                index: i,
                name: d.name.clone(),
                rule: d.rule,
                quorum: if matches!(d.rule, StageRule::Quorum) { d.quorum.max(1) } else { 0 },
                approvers: d.approvers.clone(),
                status: StageStatus::Pending,
                votes: Vec::new(),
                sla_due: None,
                activated_at: None,
                resolved_at: None,
            })
            .collect()
    }

    #[allow(clippy::too_many_arguments)]
    pub fn create_request(
        &self,
        domain: Domain,
        request_type: RequestType,
        title: String,
        description: String,
        requester: String,
        subject_ref: String,
        priority: Priority,
        workflow_id: Option<String>,
        expires_hours: Option<i64>,
    ) -> Result<ApprovalRequest, String> {
        // Resolve the workflow: explicit id, else default for the type.
        let template = match workflow_id {
            Some(id) => self.get_template(&id).ok_or_else(|| format!("Workflow not found: {id}"))?,
            None => self
                .template_for(request_type)
                .ok_or_else(|| format!("No default workflow for {request_type:?}; create one or pass workflow_id"))?,
        };
        let id = self.next_id("AR");
        let now = Utc::now();
        let mut req = ApprovalRequest {
            id: id.clone(),
            domain,
            request_type,
            title,
            description,
            status: RequestStatus::Draft,
            requester: requester.clone(),
            subject_ref,
            workflow_id: template.id.clone(),
            workflow_name: template.name.clone(),
            stages: Self::instantiate_stages(&template.stages),
            current_stage: None,
            comments: Vec::new(),
            attachments: Vec::new(),
            delegations: Vec::new(),
            audit_trail: Vec::new(),
            tags: Vec::new(),
            priority,
            expires_at: expires_hours.map(|h| now + Duration::hours(h)),
            decided_at: None,
            created_at: now,
            updated_at: now,
        };
        Self::audit(&mut req, &requester, "created", format!("via workflow {}", template.name));
        self.requests.lock().unwrap().insert(id.clone(), req.clone());
        Ok(req)
    }

    pub fn get_request(&self, id: &str) -> Option<ApprovalRequest> {
        self.requests.lock().unwrap().get(id).cloned()
    }

    pub fn search_requests(
        &self,
        query: Option<&str>,
        domain: Option<Domain>,
        request_type: Option<RequestType>,
        status: Option<RequestStatus>,
        approver: Option<&str>,
    ) -> Vec<ApprovalRequest> {
        let q = query.map(|s| s.to_lowercase());
        let mut out: Vec<ApprovalRequest> = self
            .requests
            .lock()
            .unwrap()
            .values()
            .filter(|r| {
                q.as_ref().is_none_or(|qq| r.title.to_lowercase().contains(qq) || r.description.to_lowercase().contains(qq))
                    && domain.is_none_or(|d| r.domain == d)
                    && request_type.is_none_or(|t| r.request_type == t)
                    && status.is_none_or(|s| r.status == s)
                    && approver.is_none_or(|a| r.stages.iter().any(|st| st.approvers.iter().any(|x| x == a)))
            })
            .cloned()
            .collect();
        out.sort_by(|a, b| b.created_at.cmp(&a.created_at));
        out
    }

    /// Requests where `approver` can currently act (active stage, eligible, not yet voted).
    pub fn pending_for_approver(&self, approver: &str) -> Vec<ApprovalRequest> {
        self.requests
            .lock()
            .unwrap()
            .values()
            .filter(|r| {
                r.status == RequestStatus::InReview
                    && r.current_stage.is_some_and(|ci| {
                        let st = &r.stages[ci];
                        st.approvers.iter().any(|a| a == approver)
                            && !st.votes.iter().any(|v| v.approver == approver)
                    })
            })
            .cloned()
            .collect()
    }

    // ── lifecycle ──────────────────────────────────────────────────────────

    /// Submit a draft for review: activate the first stage.
    pub fn submit(&self, id: &str, actor: &str) -> Result<ApprovalRequest, String> {
        let mut reqs = self.requests.lock().unwrap();
        let r = reqs.get_mut(id).ok_or_else(|| format!("Request not found: {id}"))?;
        if !matches!(r.status, RequestStatus::Draft | RequestStatus::ChangesRequested) {
            return Err(format!("Cannot submit from status {:?}", r.status));
        }
        if r.stages.is_empty() {
            return Err("Workflow has no stages".into());
        }
        // Reset stages to pending, then activate the first.
        for st in r.stages.iter_mut() {
            st.status = StageStatus::Pending;
            st.votes.clear();
            st.activated_at = None;
            st.resolved_at = None;
        }
        Self::activate_stage(r, 0);
        r.status = RequestStatus::InReview;
        r.current_stage = Some(0);
        Self::audit(r, actor, "submitted", "routed for review");
        Ok(r.clone())
    }

    fn activate_stage(r: &mut ApprovalRequest, idx: usize) {
        let st = &mut r.stages[idx];
        st.status = StageStatus::Active;
        st.activated_at = Some(Utc::now());
    }

    /// Record an approver's decision and evaluate the stage rule.
    pub fn decide(&self, id: &str, approver: &str, decision: Decision, comment: Option<String>) -> Result<ApprovalRequest, String> {
        let mut reqs = self.requests.lock().unwrap();
        let r = reqs.get_mut(id).ok_or_else(|| format!("Request not found: {id}"))?;
        if r.status != RequestStatus::InReview {
            return Err(format!("Request is not in review (status {:?})", r.status));
        }
        let ci = r.current_stage.ok_or("No active stage")?;

        // Resolve delegation: who is `approver` allowed to act for?
        let delegated_from: Option<String> = r
            .delegations
            .iter()
            .find(|d| d.to == approver)
            .map(|d| d.from.clone());

        let eligible_as: Option<String> = {
            let st = &r.stages[ci];
            if st.approvers.iter().any(|a| a == approver) {
                Some(approver.to_string())
            } else if let Some(f) = &delegated_from {
                if st.approvers.iter().any(|a| a == f) { Some(f.clone()) } else { None }
            } else {
                None
            }
        };
        let acting_as = eligible_as.ok_or_else(|| format!("{approver} is not an eligible approver for the active stage"))?;

        {
            let st = &mut r.stages[ci];
            if st.votes.iter().any(|v| v.approver == acting_as) {
                return Err(format!("A decision was already recorded for {acting_as} on this stage"));
            }
            st.votes.push(Vote {
                approver: acting_as.clone(),
                decision,
                comment: comment.clone(),
                on_behalf_of: if acting_as != approver { Some(acting_as.clone()) } else { None },
                at: Utc::now(),
            });
        }
        Self::audit(r, approver, "decision", format!("stage {ci} ({}) -> {decision:?}", r.stages[ci].name));

        // Evaluate the stage now that a vote landed.
        Self::evaluate_stage(r, ci);
        Ok(r.clone())
    }

    /// Apply the stage rule to its votes and advance the request as needed.
    fn evaluate_stage(r: &mut ApprovalRequest, ci: usize) {
        // Hard stops first: any reject fails the stage+request; any
        // request-changes bounces the whole request back to the requester.
        let (rule, quorum) = { let st = &r.stages[ci]; (st.rule, st.quorum) };
        let votes = r.stages[ci].votes.clone();

        if votes.iter().any(|v| v.decision == Decision::Reject) {
            r.stages[ci].status = StageStatus::Rejected;
            r.stages[ci].resolved_at = Some(Utc::now());
            r.status = RequestStatus::Rejected;
            r.decided_at = Some(Utc::now());
            r.current_stage = None;
            Self::audit(r, "engine", "rejected", format!("stage {ci} rejected"));
            return;
        }
        if votes.iter().any(|v| v.decision == Decision::RequestChanges) {
            r.stages[ci].status = StageStatus::ChangesRequested;
            r.stages[ci].resolved_at = Some(Utc::now());
            r.status = RequestStatus::ChangesRequested;
            r.current_stage = None;
            Self::audit(r, "engine", "changes_requested", format!("stage {ci} requested changes"));
            return;
        }

        let approvals = votes.iter().filter(|v| v.decision == Decision::Approve).count() as u32;
        let eligible = r.stages[ci].approvers.len() as u32;
        let decided = votes.iter().filter(|v| v.decision != Decision::Abstain).count() as u32;

        let stage_approved = match rule {
            StageRule::AnyOf => approvals >= 1,
            StageRule::Quorum => approvals >= quorum,
            StageRule::AllOf => approvals >= eligible && eligible > 0,
        };

        // For all-of, if everyone has decided but not all approved (only
        // abstentions remain), it can't pass — but rejections were handled
        // above, so this only matters when abstentions block unanimity.
        let all_decided_cannot_pass = matches!(rule, StageRule::AllOf) && decided >= eligible && approvals < eligible;

        if stage_approved {
            r.stages[ci].status = StageStatus::Approved;
            r.stages[ci].resolved_at = Some(Utc::now());
            Self::audit(r, "engine", "stage_approved", format!("stage {ci} ({}) approved", r.stages[ci].name));
            // Advance to the next pending stage, or finish.
            if ci + 1 < r.stages.len() {
                let next = ci + 1;
                Self::activate_stage(r, next);
                r.current_stage = Some(next);
                Self::audit(r, "engine", "stage_activated", format!("stage {next} ({}) active", r.stages[next].name));
            } else {
                r.status = RequestStatus::Approved;
                r.decided_at = Some(Utc::now());
                r.current_stage = None;
                Self::audit(r, "engine", "approved", "all stages approved");
            }
        } else if all_decided_cannot_pass {
            r.stages[ci].status = StageStatus::Rejected;
            r.stages[ci].resolved_at = Some(Utc::now());
            r.status = RequestStatus::Rejected;
            r.decided_at = Some(Utc::now());
            r.current_stage = None;
            Self::audit(r, "engine", "rejected", format!("stage {ci} could not reach approval (abstentions)"));
        }
    }

    pub fn cancel(&self, id: &str, actor: &str, reason: &str) -> Result<ApprovalRequest, String> {
        let mut reqs = self.requests.lock().unwrap();
        let r = reqs.get_mut(id).ok_or_else(|| format!("Request not found: {id}"))?;
        if matches!(r.status, RequestStatus::Approved | RequestStatus::Rejected | RequestStatus::Cancelled) {
            return Err(format!("Cannot cancel a {:?} request", r.status));
        }
        r.status = RequestStatus::Cancelled;
        r.current_stage = None;
        r.decided_at = Some(Utc::now());
        Self::audit(r, actor, "cancelled", reason);
        Ok(r.clone())
    }

    /// Reassign the active stage to a different queue of approvers (escalation).
    pub fn escalate(&self, id: &str, actor: &str, new_approvers: Vec<String>, reason: &str) -> Result<ApprovalRequest, String> {
        let mut reqs = self.requests.lock().unwrap();
        let r = reqs.get_mut(id).ok_or_else(|| format!("Request not found: {id}"))?;
        let ci = r.current_stage.ok_or("No active stage to escalate")?;
        r.stages[ci].approvers = new_approvers.clone();
        Self::audit(r, actor, "escalated", format!("stage {ci} -> [{}] ({reason})", new_approvers.join(", ")));
        Ok(r.clone())
    }

    // ── annotations ──────────────────────────────────────────────────────

    pub fn add_comment(&self, id: &str, author: &str, body: &str) -> Result<Comment, String> {
        let mut reqs = self.requests.lock().unwrap();
        let r = reqs.get_mut(id).ok_or_else(|| format!("Request not found: {id}"))?;
        let c = Comment { id: format!("C-{}", &Uuid::new_v4().simple().to_string()[..8]), author: author.to_string(), body: body.to_string(), created_at: Utc::now() };
        r.comments.push(c.clone());
        Self::audit(r, author, "comment", "added");
        Ok(c)
    }

    pub fn add_attachment(&self, id: &str, actor: &str, kind: &str, description: &str, uri: &str) -> Result<Attachment, String> {
        let mut reqs = self.requests.lock().unwrap();
        let r = reqs.get_mut(id).ok_or_else(|| format!("Request not found: {id}"))?;
        let a = Attachment { id: format!("AT-{}", &Uuid::new_v4().simple().to_string()[..8]), kind: kind.to_string(), description: description.to_string(), uri: uri.to_string(), added_by: actor.to_string(), added_at: Utc::now() };
        r.attachments.push(a.clone());
        Self::audit(r, actor, "attachment", format!("{kind}: {uri}"));
        Ok(a)
    }

    pub fn delegate(&self, id: &str, from: &str, to: &str, reason: Option<String>) -> Result<ApprovalRequest, String> {
        let mut reqs = self.requests.lock().unwrap();
        let r = reqs.get_mut(id).ok_or_else(|| format!("Request not found: {id}"))?;
        r.delegations.push(Delegation { from: from.to_string(), to: to.to_string(), reason: reason.clone(), created_at: Utc::now() });
        Self::audit(r, from, "delegated", format!("to {to}"));
        Ok(r.clone())
    }

    pub fn get_audit_trail(&self, id: &str) -> Option<Vec<AuditEntry>> {
        self.requests.lock().unwrap().get(id).map(|r| r.audit_trail.clone())
    }

    // ── seed ────────────────────────────────────────────────────────────

    fn seed_templates(&self) {
        let mut t = self.templates.lock().unwrap();
        let mk = |id: &str, name: &str, domain, rt, stages: Vec<StageDef>| WorkflowTemplate {
            id: id.into(), name: name.into(), domain, request_type: rt, stages, created_at: Utc::now(),
        };
        let def = |name: &str, rule, quorum, approvers: &[&str], sla: Option<i64>| StageDef {
            name: name.into(), rule, quorum, approvers: approvers.iter().map(|s| s.to_string()).collect(), sla_hours: sla,
        };

        for tpl in [
            mk("WF-CONTRACT", "Contract Review (Legal→Finance→Exec)", Domain::Legal, RequestType::ContractReview, vec![
                def("Legal Review", StageRule::AllOf, 0, &["legal.counsel"], Some(48)),
                def("Finance Review", StageRule::AnyOf, 0, &["finance.controller", "finance.cfo"], Some(48)),
                def("Executive Sign-off", StageRule::AnyOf, 0, &["exec.coo", "exec.ceo"], Some(72)),
            ]),
            mk("WF-BOARD", "Board Written Consent (quorum)", Domain::Legal, RequestType::BoardConsent, vec![
                def("Board Vote", StageRule::Quorum, 3, &["dir.1", "dir.2", "dir.3", "dir.4", "dir.5"], Some(168)),
            ]),
            mk("WF-POLICY", "Policy Compliance Review", Domain::Legal, RequestType::PolicyReview, vec![
                def("Compliance Review", StageRule::AllOf, 0, &["compliance.officer"], Some(72)),
            ]),
            mk("WF-BRAND", "Brand Compliance Sign-off", Domain::MediaContent, RequestType::BrandCompliance, vec![
                def("Brand Review", StageRule::AnyOf, 0, &["brand.lead", "brand.manager"], Some(24)),
            ]),
            mk("WF-LOCALIZE", "Localization Review", Domain::MediaContent, RequestType::ContentLocalization, vec![
                def("Linguistic QA", StageRule::AllOf, 0, &["loc.linguist"], Some(48)),
                def("In-country Review", StageRule::AnyOf, 0, &["loc.incountry"], Some(48)),
            ]),
            mk("WF-PUBQA", "Publishing QA Gate", Domain::MediaContent, RequestType::PublishingQa, vec![
                def("Editorial QA", StageRule::AllOf, 0, &["editorial.qa"], Some(24)),
            ]),
            mk("WF-RIGHTS", "Rights Clearance", Domain::MediaContent, RequestType::RightsClearance, vec![
                def("Rights Review", StageRule::AllOf, 0, &["rights.specialist"], Some(72)),
                def("Legal Clearance", StageRule::AnyOf, 0, &["legal.counsel"], Some(72)),
            ]),
            mk("WF-SOCIAL", "Social Post Review", Domain::MediaContent, RequestType::SocialPost, vec![
                def("Content Review", StageRule::AnyOf, 0, &["social.editor", "social.manager"], Some(8)),
            ]),
        ] {
            t.insert(tpl.id.clone(), tpl);
        }
    }

    fn seed_requests(&self) {
        // A contract review already in flight.
        let _ = self.create_request(
            Domain::Legal, RequestType::ContractReview,
            "MSA with Acme Corp".into(),
            "Master services agreement, 2-year term, auto-renew.".into(),
            "legalops".into(), "contract:MSA-ACME-2026".into(), Priority::High, None, Some(168),
        ).and_then(|r| self.submit(&r.id, "legalops"));

        // A social post awaiting review.
        let _ = self.create_request(
            Domain::MediaContent, RequestType::SocialPost,
            "Launch-day X thread".into(),
            "5-post thread announcing the spring release.".into(),
            "social.producer".into(), "post:SPR-LAUNCH-01".into(), Priority::Medium, None, Some(24),
        ).and_then(|r| self.submit(&r.id, "social.producer"));
    }
}
