//! Sealed playbook validator.
//!
//! PRD checks:
//!   - Operation format (`fill: Role xN in City, ST`)
//!   - endorsed_names non-empty, ≤ target_count × 2
//!   - fingerprint populated (Phase 25 validity window requirement)

use crate::{Artifact, Report, Validator, ValidationError};
use std::time::Instant;

pub struct PlaybookValidator;

impl Validator for PlaybookValidator {
    fn name(&self) -> &'static str { "staffing.playbook" }

    fn validate(&self, artifact: &Artifact) -> Result<Report, ValidationError> {
        let started = Instant::now();
        let value = match artifact {
            Artifact::Playbook(v) => v,
            other => return Err(ValidationError::Schema {
                field: "artifact".into(),
                reason: format!("PlaybookValidator expects Playbook, got {other:?}"),
            }),
        };

        // Operation format: "fill: Role xN in City, ST" — at minimum
        // we check the string-shape. Fuller grammar parse lives in
        // phase 25 code where operations are structured beyond strings.
        let op = value.get("operation").and_then(|v| v.as_str()).ok_or(
            ValidationError::Schema {
                field: "operation".into(),
                reason: "missing or not a string".into(),
            },
        )?;
        if !op.starts_with("fill:") {
            return Err(ValidationError::Schema {
                field: "operation".into(),
                reason: format!("expected `fill: ...` prefix, got {op:?}"),
            });
        }

        let endorsed = value.get("endorsed_names").and_then(|v| v.as_array()).ok_or(
            ValidationError::Schema {
                field: "endorsed_names".into(),
                reason: "missing or not an array".into(),
            },
        )?;
        if endorsed.is_empty() {
            return Err(ValidationError::Completeness {
                reason: "endorsed_names must be non-empty".into(),
            });
        }

        if let Some(target) = value.get("target_count").and_then(|v| v.as_u64()) {
            let max = (target * 2) as usize;
            if endorsed.len() > max {
                return Err(ValidationError::Completeness {
                    reason: format!(
                        "endorsed_names ({}) exceeds target_count × 2 ({max})",
                        endorsed.len()
                    ),
                });
            }
        }

        if value.get("fingerprint").and_then(|v| v.as_str()).map_or(true, |s| s.is_empty()) {
            return Err(ValidationError::Schema {
                field: "fingerprint".into(),
                reason: "missing — required for Phase 25 validity window".into(),
            });
        }

        Ok(Report {
            findings: vec![],
            elapsed_ms: started.elapsed().as_millis() as u64,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn well_formed_playbook_passes() {
        let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
            "operation": "fill: Welder x2 in Toledo, OH",
            "endorsed_names": ["W-123", "W-456"],
            "target_count": 2,
            "fingerprint": "abc123"
        })));
        assert!(r.is_ok(), "got {:?}", r);
    }

    #[test]
    fn empty_endorsed_names_fails_completeness() {
        let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
            "operation": "fill: Welder x2 in Toledo, OH",
            "endorsed_names": [],
            "fingerprint": "abc"
        })));
        assert!(matches!(r, Err(ValidationError::Completeness { .. })));
    }

    #[test]
    fn overfull_endorsed_names_fails_completeness() {
        let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
            "operation": "fill: Welder x1 in Toledo, OH",
            "endorsed_names": ["a", "b", "c"],
            "target_count": 1,
            "fingerprint": "abc"
        })));
        assert!(matches!(r, Err(ValidationError::Completeness { .. })));
    }

    #[test]
    fn missing_fingerprint_fails_schema() {
        let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
            "operation": "fill: X x1 in A, B",
            "endorsed_names": ["a"]
        })));
        assert!(matches!(r, Err(ValidationError::Schema { field, .. }) if field == "fingerprint"));
    }

    #[test]
    fn wrong_operation_prefix_fails_schema() {
        let r = PlaybookValidator.validate(&Artifact::Playbook(serde_json::json!({
            "operation": "sms_draft: hello",
            "endorsed_names": ["a"],
            "fingerprint": "x"
        })));
        assert!(matches!(r, Err(ValidationError::Schema { .. })));
    }
}
