use crate::models::*;
use crate::source_meta;
use std::collections::{BTreeMap, BTreeSet};

pub fn evidence_report_markdown(bundle: &SkillBundle) -> String {
    let source_by_id = bundle
        .sources
        .iter()
        .map(|source| (source.source_id.as_str(), source))
        .collect::<BTreeMap<_, _>>();
    let section_source = bundle
        .sections
        .iter()
        .map(|section| (section.section_id.as_str(), section.source_id.as_str()))
        .collect::<BTreeMap<_, _>>();

    let mut out = format!("# Evidence Report: {}\n\n", bundle.package.name);
    out.push_str(&format!(
        "- Bundle ID: {}\n- Version: {}\n- Review status: {:?}\n- Publish status: {:?}\n- Skills: {}\n- Sources: {}\n\n",
        bundle.package.bundle_id,
        bundle.package.version,
        bundle.package.review_status,
        bundle.package.publish_status,
        bundle.skills.len(),
        bundle.sources.len()
    ));

    out.push_str("## Source Trust and Rights\n\n");
    let mut trust_counts: BTreeMap<String, usize> = BTreeMap::new();
    let mut right_warnings = Vec::new();
    for source in &bundle.sources {
        *trust_counts
            .entry(format!("{:?}", source_meta::infer_source_trust(source)))
            .or_default() += 1;
        if !matches!(source.permission_status, PermissionStatus::Allowed) {
            right_warnings.push(format!(
                "{} permission status is {:?}",
                source.source_id, source.permission_status
            ));
        }
        if !matches!(source.secret_scan_status, ScanStatus::Clean) {
            right_warnings.push(format!(
                "{} has unresolved secret scan status {:?}",
                source.source_id, source.secret_scan_status
            ));
        }
        if !matches!(source.export_policy, ExportPolicy::PublicAllowed) {
            right_warnings.push(format!(
                "{} export policy is {:?}",
                source.source_id, source.export_policy
            ));
        }
    }
    if trust_counts.is_empty() {
        out.push_str("- No sources recorded.\n");
    } else {
        for (trust, count) in trust_counts {
            out.push_str(&format!("- {trust}: {count}\n"));
        }
    }
    if !right_warnings.is_empty() {
        out.push_str("\n### Source Rights Warnings\n\n");
        for warning in right_warnings {
            out.push_str(&format!("- {warning}\n"));
        }
    }
    out.push('\n');

    if !bundle.forge_requests.is_empty() || !bundle.forge_responses.is_empty() {
        out.push_str("## Forge Provider Provenance\n\n");
        let mut provider_counts: BTreeMap<String, usize> = BTreeMap::new();
        let mut adapter_mode_counts: BTreeMap<String, usize> = BTreeMap::new();
        let mut live_reasoning_count = 0usize;
        let mut deterministic_count = 0usize;
        let mut missing_provenance_count = 0usize;
        for request in &bundle.forge_requests {
            if let Some(provenance) = &request.provider_provenance {
                *provider_counts
                    .entry(provenance.provider.clone())
                    .or_default() += 1;
                *adapter_mode_counts
                    .entry(provenance.adapter_mode.clone())
                    .or_default() += 1;
                if provenance.live_reasoning {
                    live_reasoning_count += 1;
                }
                if provenance.deterministic {
                    deterministic_count += 1;
                }
            } else {
                missing_provenance_count += 1;
            }
        }
        out.push_str(&format!(
            "- Forge requests: {}\n- Forge responses: {}\n- Live reasoning requests: {}\n- Deterministic requests: {}\n- Missing provenance records: {}\n\n",
            bundle.forge_requests.len(),
            bundle.forge_responses.len(),
            live_reasoning_count,
            deterministic_count,
            missing_provenance_count
        ));
        if !provider_counts.is_empty() {
            out.push_str("### Providers\n\n");
            for (provider, count) in provider_counts {
                out.push_str(&format!("- {provider}: {count} request(s)\n"));
            }
            out.push('\n');
        }
        if !adapter_mode_counts.is_empty() {
            out.push_str("### Adapter Modes\n\n");
            for (mode, count) in adapter_mode_counts {
                out.push_str(&format!("- {mode}: {count} request(s)\n"));
            }
            out.push('\n');
        }
        if missing_provenance_count > 0 {
            out.push_str("### Forge Provenance Warnings\n\n");
            out.push_str("- Some Forge requests lack provider provenance; review the original Forge history before publication.\n\n");
        }
    }

    out.push_str("## Evidence Summary by Skill\n\n");
    for skill in &bundle.skills {
        out.push_str(&format!(
            "## {}\n\n- Skill ID: {}\n- Status: {:?}\n- Maturity: {:?}\n- Scope: {:?}\n- Type: {:?}\n- Direct extraction: {:.0}%\n- Supporting inference: {:.0}%\n- Operational synthesis: {:.0}%\n- Speculative: {:.0}%\n- Community-derived: {:.0}%\n- Internal-policy derived: {:.0}%\n- Raw confidence: {:.0}%\n- Routing confidence: {:.0}%\n- Human review confidence: {:.0}%\n- Citations: {}\n- Inference records: {}\n\n",
            skill.title,
            skill.id,
            skill.status,
            skill.maturity,
            skill.scope,
            skill.skill_type,
            skill.evidence_breakdown.direct_extraction * 100.0,
            skill.evidence_breakdown.supporting_inference * 100.0,
            skill.evidence_breakdown.operational_synthesis * 100.0,
            skill.evidence_breakdown.speculative_candidate * 100.0,
            skill.evidence_breakdown.community_derived * 100.0,
            skill.evidence_breakdown.internal_policy_derived * 100.0,
            skill.confidence.raw * 100.0,
            skill.confidence.routing * 100.0,
            skill.confidence.human_review * 100.0,
            skill.citations.len(),
            skill.inference_records.len()
        ));

        let warnings = skill_warnings(skill);
        if !warnings.is_empty() {
            out.push_str("### Evidence / Publication Warnings\n\n");
            for warning in warnings {
                out.push_str(&format!("- {warning}\n"));
            }
            out.push('\n');
        }

        if !skill.citations.is_empty() {
            out.push_str("### Citations\n\n");
            for citation in &skill.citations {
                let source_title = source_by_id
                    .get(citation.source_id.as_str())
                    .map(|source| source.title.as_str())
                    .unwrap_or("unknown source");
                let ownership = match section_source.get(citation.section_id.as_str()) {
                    Some(source_id) if *source_id == citation.source_id.as_str() => "ok",
                    Some(_) => "source/section mismatch",
                    None => "missing section",
                };
                out.push_str(&format!(
                    "- {}: source={} ({source_title}), section={}, ownership={}\n",
                    citation.citation_id, citation.source_id, citation.section_id, ownership
                ));
            }
            out.push('\n');
        }

        if !skill.inference_records.is_empty() {
            out.push_str("### Inference Records\n\n");
            for record in &skill.inference_records {
                out.push_str(&format!(
                    "- {}: {:?}, evidence={:?}, confidence={:.0}%, required_review={}, refs={}\n",
                    record.inference_id,
                    record.inference_type,
                    record.evidence_type,
                    record.confidence * 100.0,
                    record.required_review,
                    record.source_refs_used.join(", ")
                ));
                if !record.unsupported_assumptions.is_empty() {
                    out.push_str(&format!(
                        "  - Unsupported assumptions: {}\n",
                        record.unsupported_assumptions.join(" | ")
                    ));
                }
                if !record.risk_flags.is_empty() {
                    out.push_str(&format!(
                        "  - Risk flags: {}\n",
                        record.risk_flags.join(" | ")
                    ));
                }
            }
            out.push('\n');
        }

        if !skill.tool_requirements.is_empty() {
            out.push_str("### Tool Requirements\n\n");
            let mut seen = BTreeSet::new();
            for tool in &skill.tool_requirements {
                if seen.insert(tool.name.clone()) {
                    out.push_str(&format!(
                        "- {}: {:?}, permission={:?}, rollback_required={}\n",
                        tool.name,
                        tool.requirement_type,
                        tool.permission_level,
                        tool.rollback_required
                    ));
                }
            }
            out.push('\n');
        }
    }
    out
}

fn skill_warnings(skill: &Skill) -> Vec<String> {
    let mut warnings = Vec::new();
    if skill.citations.is_empty() {
        warnings.push("missing citations".into());
    }
    if skill.evidence_breakdown.speculative_candidate > 0.25 {
        warnings.push("speculative evidence exceeds 25% review threshold".into());
    }
    if skill
        .inference_records
        .iter()
        .any(|record| record.required_review)
    {
        warnings.push("contains inference records requiring review".into());
    }
    if skill.runtime_policy.modify_external_systems && !skill.runtime_policy.requires_user_approval
    {
        warnings.push("external mutation lacks user approval requirement".into());
    }
    if skill.runtime_policy.modify_files && !skill.runtime_policy.requires_backup_or_rollback {
        warnings.push("file mutation lacks backup/rollback requirement".into());
    }
    if matches!(
        skill.status,
        SkillStatus::Unsafe | SkillStatus::Archived | SkillStatus::Deprecated
    ) {
        warnings.push(format!(
            "skill status {:?} is not publishable",
            skill.status
        ));
    }
    warnings
}
