//! Fill-proposal validator.
//!
//! PRD checks:
//!   - Schema compliance (propose_done shape matches
//!     `{fills: [{candidate_id, name}]}`)
//!   - Completeness (endorsed count == target_count)
//!   - Worker existence (every candidate_id present in workers_500k)
//!   - Status check (active, not_on_client_blacklist)
//!   - Geo/role match (worker city/state/role matches contract)
//!
//! Today this is a scaffold — schema check is real (it's cheap); the
//! worker-existence / status / geo checks need a catalog lookup and
//! land in a follow-up when the catalog query helper is wired into
//! this crate.

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

pub struct FillValidator;

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

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

        // Schema check — the only real validation shipped in this
        // scaffold. Catches the common "model emitted prose instead of
        // JSON" failure mode before the consistency checks even run.
        let fills = value.get("fills").and_then(|f| f.as_array()).ok_or(
            ValidationError::Schema {
                field: "fills".into(),
                reason: "expected top-level `fills` array".into(),
            },
        )?;
        for (i, fill) in fills.iter().enumerate() {
            if fill.get("candidate_id").is_none() {
                return Err(ValidationError::Schema {
                    field: format!("fills[{i}].candidate_id"),
                    reason: "missing".into(),
                });
            }
            if fill.get("name").is_none() {
                return Err(ValidationError::Schema {
                    field: format!("fills[{i}].name"),
                    reason: "missing".into(),
                });
            }
        }

        // TODO(phase-43 v2): worker-existence / status / geo checks.
        // Need a catalog query handle injected into FillValidator's
        // constructor — out of scope for the scaffold.

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

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

    #[test]
    fn wrong_artifact_type_fails_schema() {
        let r = FillValidator.validate(&Artifact::EmailDraft(serde_json::json!({})));
        assert!(matches!(r, Err(ValidationError::Schema { .. })));
    }

    #[test]
    fn missing_fills_array_fails_schema() {
        let r = FillValidator.validate(&Artifact::FillProposal(serde_json::json!({})));
        assert!(matches!(r, Err(ValidationError::Schema { field, .. }) if field == "fills"));
    }

    #[test]
    fn fill_without_candidate_id_fails() {
        let r = FillValidator.validate(&Artifact::FillProposal(serde_json::json!({
            "fills": [{"name": "Jane"}]
        })));
        assert!(matches!(r, Err(ValidationError::Schema { field, .. }) if field.contains("candidate_id")));
    }

    #[test]
    fn well_formed_proposal_passes_schema() {
        let r = FillValidator.validate(&Artifact::FillProposal(serde_json::json!({
            "fills": [
                {"candidate_id": "W-123", "name": "Jane Doe"},
                {"candidate_id": "W-456", "name": "John Smith"}
            ]
        })));
        assert!(r.is_ok(), "well-formed proposal should pass schema: {:?}", r);
    }
}
