// Agent contract-first enforcement checks: CB-1400 through CB-1404
// Included from check.rs — do NOT add `use` imports or `#!` attributes here.
//
// Spec: docs/specifications/components/agent-integration.md (Component 10)
//
// Enforces provable-contract-first design for all agents/sub-agents.
// No agent may generate code without a prior contract (YAML or contract.json).

/// Required patterns in agent context files (CLAUDE.md, GEMINI.md, AGENTS.md)
/// that indicate contract-first methodology is documented.
const AGENT_CONTRACT_PATTERNS: &[&str] = &[
    "contract-first",
    "provable-contract",
    "NEVER write code before",
    "NEVER.*code.*before.*contract",
    "CB-1400",
    "verification_level",
    "pmat comply",
];

/// Known AI agent co-author patterns in git commits
const AI_COAUTHOR_PATTERNS: &[&str] = &[
    "Co-Authored-By: Claude",
    "Co-Authored-By: Copilot",
    "Co-Authored-By: GPT",
    "Co-Authored-By: Gemini",
    "Co-Authored-By: Cursor",
    "Co-Authored-By: Cody",
    "Co-Authored-By: Devin",
    "Co-Authored-By: Codex",
    "generated by ai",
    "ai-generated",
];

/// CB-1400: Agent Contract Existence
///
/// Checks that agent context files (CLAUDE.md, GEMINI.md, AGENTS.md) reference
/// contract-first methodology. If agent context files exist but lack contract-first
/// patterns, the project is not enforcing contract-first for agents.
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub(crate) fn check_agent_contract_existence(project_path: &Path) -> ComplianceCheck {
    let agent_files = [
        "CLAUDE.md",
        "GEMINI.md",
        "AGENTS.md",
        "AGENT.md",
        ".claude/CLAUDE.md",
    ];

    let mut found_agent_files = Vec::new();
    let mut files_with_contract_ref = Vec::new();
    let mut files_missing_contract_ref = Vec::new();

    for agent_file in &agent_files {
        let path = project_path.join(agent_file);
        if path.exists() {
            found_agent_files.push(*agent_file);
            if let Ok(content) = fs::read_to_string(&path) {
                let content_lower = content.to_lowercase();
                let has_contract_ref = AGENT_CONTRACT_PATTERNS
                    .iter()
                    .any(|p| content_lower.contains(&p.to_lowercase()));
                if has_contract_ref {
                    files_with_contract_ref.push(*agent_file);
                } else {
                    files_missing_contract_ref.push(*agent_file);
                }
            }
        }
    }

    if found_agent_files.is_empty() {
        return ComplianceCheck {
            name: "CB-1400: Agent Contract Existence".into(),
            status: CheckStatus::Skip,
            message: "No agent context files found (CLAUDE.md, AGENTS.md, etc.)".into(),
            severity: Severity::Info,
        };
    }

    if files_missing_contract_ref.is_empty() {
        ComplianceCheck {
            name: "CB-1400: Agent Contract Existence".into(),
            status: CheckStatus::Pass,
            message: format!(
                "{}/{} agent context file(s) reference contract-first design",
                files_with_contract_ref.len(),
                found_agent_files.len()
            ),
            severity: Severity::Info,
        }
    } else {
        ComplianceCheck {
            name: "CB-1400: Agent Contract Existence".into(),
            status: CheckStatus::Fail,
            message: format!(
                "{} agent context file(s) lack contract-first reference: {}",
                files_missing_contract_ref.len(),
                files_missing_contract_ref.join(", ")
            ),
            severity: Severity::Error,
        }
    }
}

/// CB-1401: Agent Contract Falsifiability
///
/// Checks that work contracts in .pmat-work/ have falsifiable claims with
/// actual evidence commands (not just descriptions). Contracts with empty
/// evidence fields or placeholder-only claims are flagged.
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub(crate) fn check_agent_contract_falsifiability(project_path: &Path) -> ComplianceCheck {
    let work_dir = project_path.join(".pmat-work");
    if !work_dir.exists() {
        return ComplianceCheck {
            name: "CB-1401: Agent Contract Falsifiability".into(),
            status: CheckStatus::Skip,
            message: "No .pmat-work/ directory found".into(),
            severity: Severity::Info,
        };
    }

    let mut total_contracts = 0usize;
    let mut contracts_with_evidence = 0usize;
    let mut contracts_without_evidence = Vec::new();

    if let Ok(entries) = fs::read_dir(&work_dir) {
        for entry in entries.flatten() {
            let contract_path = entry.path().join("contract.json");
            if !contract_path.exists() {
                continue;
            }
            total_contracts += 1;

            if let Ok(content) = fs::read_to_string(&contract_path) {
                // Check for evidence fields in the contract
                let has_evidence = content.contains("\"evidence\":")
                    && !content.contains("\"evidence\": \"\"")
                    && !content.contains("\"evidence\":\"\"");

                // Check for require/ensure/invariant clauses (DbC v5.0)
                let has_dbc = content.contains("\"require\":")
                    || content.contains("\"ensure\":")
                    || content.contains("\"invariant\":");

                // Check for falsifiable claims (v4 "claims" or v5 "falsifiable_claims")
                let has_claims = content.contains("\"claims\":")
                    || content.contains("\"falsifiable_claims\":");

                if (has_evidence && has_dbc) || has_claims {
                    contracts_with_evidence += 1;
                } else {
                    let id = entry
                        .file_name()
                        .to_string_lossy()
                        .to_string();
                    if contracts_without_evidence.len() < 5 {
                        contracts_without_evidence.push(id);
                    }
                }
            }
        }
    }

    if total_contracts == 0 {
        return ComplianceCheck {
            name: "CB-1401: Agent Contract Falsifiability".into(),
            status: CheckStatus::Skip,
            message: "No work contracts found in .pmat-work/".into(),
            severity: Severity::Info,
        };
    }

    if contracts_without_evidence.is_empty() {
        ComplianceCheck {
            name: "CB-1401: Agent Contract Falsifiability".into(),
            status: CheckStatus::Pass,
            message: format!(
                "{}/{} work contract(s) have falsifiable claims with evidence",
                contracts_with_evidence, total_contracts
            ),
            severity: Severity::Info,
        }
    } else {
        ComplianceCheck {
            name: "CB-1401: Agent Contract Falsifiability".into(),
            status: CheckStatus::Fail,
            message: format!(
                "{} contract(s) lack falsifiable evidence: {}",
                contracts_without_evidence.len(),
                contracts_without_evidence.join(", ")
            ),
            severity: Severity::Error,
        }
    }
}

/// CB-1402: Agent Verification Level Floor
///
/// Checks that work contracts specify a verification level >= L1 for
/// autonomous agents. L0 (paper-only/human review) is not acceptable
/// for autonomous agent work.
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub(crate) fn check_agent_verification_level(project_path: &Path) -> ComplianceCheck {
    let work_dir = project_path.join(".pmat-work");
    if !work_dir.exists() {
        return ComplianceCheck {
            name: "CB-1402: Agent Verification Level".into(),
            status: CheckStatus::Skip,
            message: "No .pmat-work/ directory found".into(),
            severity: Severity::Info,
        };
    }

    let mut total_contracts = 0usize;
    let mut l0_contracts = Vec::new();
    let mut no_level_contracts = Vec::new();

    if let Ok(entries) = fs::read_dir(&work_dir) {
        for entry in entries.flatten() {
            let contract_path = entry.path().join("contract.json");
            if !contract_path.exists() {
                continue;
            }
            total_contracts += 1;

            if let Ok(content) = fs::read_to_string(&contract_path) {
                let id = entry.file_name().to_string_lossy().to_string();

                if content.contains("\"verification_level\"") {
                    // Check for L0 — paper-only, unacceptable for autonomous agents
                    if (content.contains("\"L0\"") || content.contains("\"l0\""))
                        && l0_contracts.len() < 5
                    {
                        l0_contracts.push(id);
                    }
                    // L1+ is acceptable
                } else {
                    // No verification level specified — pre-v5.0 contract
                    if no_level_contracts.len() < 5 {
                        no_level_contracts.push(id);
                    }
                }
            }
        }
    }

    if total_contracts == 0 {
        return ComplianceCheck {
            name: "CB-1402: Agent Verification Level".into(),
            status: CheckStatus::Skip,
            message: "No work contracts found".into(),
            severity: Severity::Info,
        };
    }

    if !l0_contracts.is_empty() {
        ComplianceCheck {
            name: "CB-1402: Agent Verification Level".into(),
            status: CheckStatus::Fail,
            message: format!(
                "{} contract(s) at L0 (paper-only) — autonomous agents require >= L1: {}",
                l0_contracts.len(),
                l0_contracts.join(", ")
            ),
            severity: Severity::Error,
        }
    } else if !no_level_contracts.is_empty() {
        ComplianceCheck {
            name: "CB-1402: Agent Verification Level".into(),
            status: CheckStatus::Warn,
            message: format!(
                "{} contract(s) missing verification_level field: {}",
                no_level_contracts.len(),
                no_level_contracts.join(", ")
            ),
            severity: Severity::Warning,
        }
    } else {
        ComplianceCheck {
            name: "CB-1402: Agent Verification Level".into(),
            status: CheckStatus::Pass,
            message: format!(
                "{} work contract(s) at verification level >= L1",
                total_contracts
            ),
            severity: Severity::Info,
        }
    }
}

/// CB-1403: Assume-Guarantee Chain Validation
///
/// For multi-agent workflows, validates that work contract ensure clauses
/// from one phase align with require clauses of the next phase.
/// Currently checks that contracts with "parent" or "depends_on" references
/// have matching clause structure.
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub(crate) fn check_assume_guarantee_chain(project_path: &Path) -> ComplianceCheck {
    let work_dir = project_path.join(".pmat-work");
    if !work_dir.exists() {
        return ComplianceCheck {
            name: "CB-1403: Assume-Guarantee Chain".into(),
            status: CheckStatus::Skip,
            message: "No .pmat-work/ directory found".into(),
            severity: Severity::Info,
        };
    }

    // Collect contracts that reference other work items (chain indicators)
    let mut chained_contracts = 0usize;
    let mut total_contracts = 0usize;

    if let Ok(entries) = fs::read_dir(&work_dir) {
        for entry in entries.flatten() {
            let contract_path = entry.path().join("contract.json");
            if !contract_path.exists() {
                continue;
            }
            total_contracts += 1;

            if let Ok(content) = fs::read_to_string(&contract_path) {
                // Look for chain indicators: iteration > 1, parent references, or
                // require clauses that reference other ticket IDs
                let has_chain_ref = content.contains("\"iteration\":")
                    && !content.contains("\"iteration\": 1")
                    && !content.contains("\"iteration\":1");
                let has_parent = content.contains("\"parent_agent\"")
                    || content.contains("\"depends_on\"");
                let has_refs_pattern = content.contains("Refs PMAT-")
                    || content.contains("refs PMAT-");

                if has_chain_ref || has_parent || has_refs_pattern {
                    chained_contracts += 1;
                }
            }
        }
    }

    if total_contracts == 0 {
        return ComplianceCheck {
            name: "CB-1403: Assume-Guarantee Chain".into(),
            status: CheckStatus::Skip,
            message: "No work contracts found".into(),
            severity: Severity::Info,
        };
    }

    // Info-level: report chain status. Future: validate clause alignment.
    ComplianceCheck {
        name: "CB-1403: Assume-Guarantee Chain".into(),
        status: CheckStatus::Pass,
        message: format!(
            "{}/{} contract(s) participate in assume-guarantee chains",
            chained_contracts, total_contracts
        ),
        severity: Severity::Info,
    }
}

/// CB-1404: Agent Comply Check Usage
///
/// Checks that work contracts have falsification receipts — evidence that
/// `pmat comply check` or `pmat work checkpoint` was run during the task.
/// Agents that complete without running comply are operating blind.
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub(crate) fn check_agent_comply_usage(project_path: &Path) -> ComplianceCheck {
    let work_dir = project_path.join(".pmat-work");
    if !work_dir.exists() {
        return ComplianceCheck {
            name: "CB-1404: Agent Comply Usage".into(),
            status: CheckStatus::Skip,
            message: "No .pmat-work/ directory found".into(),
            severity: Severity::Info,
        };
    }

    let mut total_contracts = 0usize;
    let mut contracts_with_receipts = 0usize;

    if let Ok(entries) = fs::read_dir(&work_dir) {
        for entry in entries.flatten() {
            let contract_path = entry.path().join("contract.json");
            if !contract_path.exists() {
                continue;
            }
            total_contracts += 1;

            // Check for evidence of comply/checkpoint run:
            // - falsification/ dir with receipts (from pmat work complete)
            // - checkpoints/ dir with checkpoint files (from pmat work checkpoint)
            // - trend/ dir with metrics snapshots
            let has_falsification = entry.path().join("falsification").exists()
                && fs::read_dir(entry.path().join("falsification"))
                    .map(|d| d.count() > 0)
                    .unwrap_or(false);
            let has_checkpoints = entry.path().join("checkpoints").exists()
                && fs::read_dir(entry.path().join("checkpoints"))
                    .map(|d| d.count() > 0)
                    .unwrap_or(false);

            if has_falsification || has_checkpoints {
                contracts_with_receipts += 1;
            }
        }
    }

    if total_contracts == 0 {
        return ComplianceCheck {
            name: "CB-1404: Agent Comply Usage".into(),
            status: CheckStatus::Skip,
            message: "No work contracts found".into(),
            severity: Severity::Info,
        };
    }

    let ratio = contracts_with_receipts as f64 / total_contracts as f64;
    if ratio >= 0.8 {
        ComplianceCheck {
            name: "CB-1404: Agent Comply Usage".into(),
            status: CheckStatus::Pass,
            message: format!(
                "{}/{} contract(s) have falsification receipts (comply was run)",
                contracts_with_receipts, total_contracts
            ),
            severity: Severity::Info,
        }
    } else {
        ComplianceCheck {
            name: "CB-1404: Agent Comply Usage".into(),
            status: CheckStatus::Warn,
            message: format!(
                "Only {}/{} contract(s) have receipts — agents should run pmat comply before completing",
                contracts_with_receipts, total_contracts
            ),
            severity: Severity::Warning,
        }
    }
}


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

    // --- CB-1400 tests ---

    #[test]
    fn test_cb1400_skip_no_agent_files() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let result = check_agent_contract_existence(tmp.path());
        assert_eq!(result.status, CheckStatus::Skip);
        assert!(result.message.contains("No agent context files"));
    }

    #[test]
    fn test_cb1400_pass_claude_md_with_contract_ref() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        std::fs::write(
            tmp.path().join("CLAUDE.md"),
            "# Instructions\n\nUse contract-first design.\npmat comply check required.\n",
        )
        .unwrap();
        let result = check_agent_contract_existence(tmp.path());
        assert_eq!(result.status, CheckStatus::Pass);
        assert!(result.message.contains("1/1"));
    }

    #[test]
    fn test_cb1400_fail_agent_file_without_contract_ref() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        std::fs::write(
            tmp.path().join("CLAUDE.md"),
            "# Instructions\n\nJust write code.\n",
        )
        .unwrap();
        let result = check_agent_contract_existence(tmp.path());
        assert_eq!(result.status, CheckStatus::Fail);
        assert!(result.message.contains("CLAUDE.md"));
    }

    #[test]
    fn test_cb1400_mixed_agent_files() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        std::fs::write(
            tmp.path().join("CLAUDE.md"),
            "# Instructions\nUse contract-first approach.\n",
        )
        .unwrap();
        std::fs::write(
            tmp.path().join("AGENTS.md"),
            "# Agent Protocol\nNo contract references here.\n",
        )
        .unwrap();
        let result = check_agent_contract_existence(tmp.path());
        assert_eq!(result.status, CheckStatus::Fail);
        assert!(result.message.contains("AGENTS.md"));
    }

    // --- CB-1401 tests ---

    #[test]
    fn test_cb1401_skip_no_work_dir() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let result = check_agent_contract_falsifiability(tmp.path());
        assert_eq!(result.status, CheckStatus::Skip);
    }

    #[test]
    fn test_cb1401_pass_contract_with_evidence() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let work_dir = tmp.path().join(".pmat-work").join("PMAT-001");
        std::fs::create_dir_all(&work_dir).unwrap();
        std::fs::write(
            work_dir.join("contract.json"),
            r#"{"require": [{"description": "builds", "evidence": "cargo build"}],
                "ensure": [{"description": "tests pass", "evidence": "cargo test"}],
                "claims": [{"method": "Test"}]}"#,
        )
        .unwrap();
        let result = check_agent_contract_falsifiability(tmp.path());
        assert_eq!(result.status, CheckStatus::Pass);
    }

    #[test]
    fn test_cb1401_fail_contract_without_evidence() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let work_dir = tmp.path().join(".pmat-work").join("PMAT-001");
        std::fs::create_dir_all(&work_dir).unwrap();
        std::fs::write(
            work_dir.join("contract.json"),
            r#"{"title": "Just a title", "status": "planned"}"#,
        )
        .unwrap();
        let result = check_agent_contract_falsifiability(tmp.path());
        assert_eq!(result.status, CheckStatus::Fail);
        assert!(result.message.contains("PMAT-001"));
    }

    // --- CB-1402 tests ---

    #[test]
    fn test_cb1402_skip_no_work_dir() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let result = check_agent_verification_level(tmp.path());
        assert_eq!(result.status, CheckStatus::Skip);
    }

    #[test]
    fn test_cb1402_pass_l3_contract() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let work_dir = tmp.path().join(".pmat-work").join("PMAT-002");
        std::fs::create_dir_all(&work_dir).unwrap();
        std::fs::write(
            work_dir.join("contract.json"),
            r#"{"verification_level": "L3", "require": []}"#,
        )
        .unwrap();
        let result = check_agent_verification_level(tmp.path());
        assert_eq!(result.status, CheckStatus::Pass);
    }

    #[test]
    fn test_cb1402_fail_l0_contract() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let work_dir = tmp.path().join(".pmat-work").join("PMAT-003");
        std::fs::create_dir_all(&work_dir).unwrap();
        std::fs::write(
            work_dir.join("contract.json"),
            r#"{"verification_level": "L0", "require": []}"#,
        )
        .unwrap();
        let result = check_agent_verification_level(tmp.path());
        assert_eq!(result.status, CheckStatus::Fail);
        assert!(result.message.contains("L0"));
    }

    #[test]
    fn test_cb1402_warn_no_level() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let work_dir = tmp.path().join(".pmat-work").join("PMAT-004");
        std::fs::create_dir_all(&work_dir).unwrap();
        std::fs::write(
            work_dir.join("contract.json"),
            r#"{"require": [], "ensure": []}"#,
        )
        .unwrap();
        let result = check_agent_verification_level(tmp.path());
        assert_eq!(result.status, CheckStatus::Warn);
        assert!(result.message.contains("missing verification_level"));
    }


    // --- CB-1403 tests ---

    #[test]
    fn test_cb1403_skip_no_work_dir() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let result = check_assume_guarantee_chain(tmp.path());
        assert_eq!(result.status, CheckStatus::Skip);
    }

    #[test]
    fn test_cb1403_pass_with_refs() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let work_dir = tmp.path().join(".pmat-work").join("PMAT-005");
        std::fs::create_dir_all(&work_dir).unwrap();
        std::fs::write(
            work_dir.join("contract.json"),
            r#"{"title": "Test", "iteration": 2, "require": []}"#,
        )
        .unwrap();
        let result = check_assume_guarantee_chain(tmp.path());
        assert_eq!(result.status, CheckStatus::Pass);
        assert!(result.message.contains("1/1"));
    }


    // --- CB-1404 tests ---

    #[test]
    fn test_cb1404_skip_no_work_dir() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let result = check_agent_comply_usage(tmp.path());
        assert_eq!(result.status, CheckStatus::Skip);
    }

    #[test]
    fn test_cb1404_pass_with_receipts() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let work_dir = tmp.path().join(".pmat-work").join("PMAT-010");
        let receipt_dir = work_dir.join("falsification");
        std::fs::create_dir_all(&receipt_dir).unwrap();
        std::fs::write(work_dir.join("contract.json"), r#"{"version": "5.0"}"#).unwrap();
        std::fs::write(receipt_dir.join("receipt-1.json"), "{}").unwrap();
        let result = check_agent_comply_usage(tmp.path());
        assert_eq!(result.status, CheckStatus::Pass);
    }

    #[test]
    fn test_cb1404_pass_with_checkpoints() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let work_dir = tmp.path().join(".pmat-work").join("PMAT-010b");
        let checkpoint_dir = work_dir.join("checkpoints");
        std::fs::create_dir_all(&checkpoint_dir).unwrap();
        std::fs::write(work_dir.join("contract.json"), r#"{"version": "5.0"}"#).unwrap();
        std::fs::write(checkpoint_dir.join("checkpoint-1.json"), "{}").unwrap();
        let result = check_agent_comply_usage(tmp.path());
        assert_eq!(result.status, CheckStatus::Pass);
    }

    #[test]
    fn test_cb1404_warn_no_receipts() {
        let tmp = tempfile::tempdir().expect("create tempdir");
        let work_dir = tmp.path().join(".pmat-work").join("PMAT-011");
        std::fs::create_dir_all(&work_dir).unwrap();
        std::fs::write(work_dir.join("contract.json"), r#"{"version": "5.0"}"#).unwrap();
        let result = check_agent_comply_usage(tmp.path());
        assert_eq!(result.status, CheckStatus::Warn);
    }

}
