use crate::audit::DiscoveredFrom;
use crate::audit::PublicationPointAudit;
use crate::data_model::rc::{AsResourceSet, IpResourceSet};
use crate::report::Warning;
use crate::validation::manifest::PublicationPointSource;
use crate::validation::objects::{AspaAttestation, ObjectsOutput, RouterKeyPayload, Vrp};
use crate::validation::publication_point::PublicationPointSnapshot;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TreeRunConfig {
    /// Max CA instance depth to process (0 = root only).
    pub max_depth: Option<usize>,
    /// Max number of CA instances to process.
    pub max_instances: Option<usize>,
    /// Drop per-object audit payload when the caller only needs validation outputs.
    pub compact_audit: bool,
    /// Persist per-CA VCIR state for future reuse/delta runs.
    pub persist_vcir: bool,
    /// Build online CCR manifest projections during phase2 validation.
    pub build_ccr_accumulator: bool,
}

impl Default for TreeRunConfig {
    fn default() -> Self {
        Self {
            max_depth: None,
            max_instances: None,
            compact_audit: false,
            persist_vcir: true,
            build_ccr_accumulator: true,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CaInstanceHandle {
    pub depth: usize,
    pub tal_id: String,
    pub parent_manifest_rsync_uri: Option<String>,
    /// DER bytes of the CA certificate for this CA instance.
    pub ca_certificate_der: Vec<u8>,
    /// rsync URI of this CA certificate object (where it is published).
    ///
    /// This is used for strict AIA binding checks (RFC 6487 §4.8.7) when validating
    /// subordinate CA and EE certificates. Trust anchor certificates downloaded from
    /// TAL URIs may not have an rsync publication URI, so this can be `None`.
    pub ca_certificate_rsync_uri: Option<String>,
    /// Effective (fully resolved) resources for this CA instance, used for resource-path
    /// validation of subordinate CA certificates.
    pub effective_ip_resources: Option<IpResourceSet>,
    pub effective_as_resources: Option<AsResourceSet>,
    pub rsync_base_uri: String,
    pub manifest_rsync_uri: String,
    pub publication_point_rsync_uri: String,
    pub rrdp_notification_uri: Option<String>,
}

impl CaInstanceHandle {
    pub fn with_depth(mut self, depth: usize) -> Self {
        self.depth = depth;
        self
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicationPointRunResult {
    pub source: PublicationPointSource,
    pub snapshot: Option<PublicationPointSnapshot>,
    pub warnings: Vec<Warning>,
    pub objects: ObjectsOutput,
    pub audit: PublicationPointAudit,
    /// Candidate child CA instances to enqueue after this publication point completes.
    ///
    /// - For `Fresh`, these are discovered from the current validated publication point.
    /// - For `VcirCurrentInstance`, these are restored from the current instance VCIR and then
    ///   processed fresh-first when their own turn arrives.
    pub discovered_children: Vec<DiscoveredChildCaInstance>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiscoveredChildCaInstance {
    pub handle: CaInstanceHandle,
    pub discovered_from: DiscoveredFrom,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TreeRunOutput {
    pub instances_processed: usize,
    pub instances_failed: usize,
    pub warnings: Vec<Warning>,
    pub vrps: Vec<Vrp>,
    pub aspas: Vec<AspaAttestation>,
    pub router_keys: Vec<RouterKeyPayload>,
}

#[derive(Debug, thiserror::Error)]
pub enum TreeRunError {
    #[error("publication point runner failed: {0}")]
    Runner(String),
}

pub trait PublicationPointRunner {
    fn run_publication_point(
        &self,
        ca: &CaInstanceHandle,
    ) -> Result<PublicationPointRunResult, String>;

    fn prefetch_discovered_children(
        &self,
        _children: &[DiscoveredChildCaInstance],
    ) -> Result<(), String> {
        Ok(())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TreeRunAuditOutput {
    pub tree: TreeRunOutput,
    pub publication_points: Vec<PublicationPointAudit>,
}

pub fn run_tree_serial(
    root: CaInstanceHandle,
    runner: &dyn PublicationPointRunner,
    config: &TreeRunConfig,
) -> Result<TreeRunOutput, TreeRunError> {
    Ok(run_tree_serial_audit(root, runner, config)?.tree)
}

pub fn run_tree_serial_audit(
    root: CaInstanceHandle,
    runner: &dyn PublicationPointRunner,
    config: &TreeRunConfig,
) -> Result<TreeRunAuditOutput, TreeRunError> {
    run_tree_serial_audit_multi_root(vec![root], runner, config)
}

pub fn run_tree_serial_audit_multi_root(
    roots: Vec<CaInstanceHandle>,
    runner: &dyn PublicationPointRunner,
    config: &TreeRunConfig,
) -> Result<TreeRunAuditOutput, TreeRunError> {
    #[derive(Clone, Debug)]
    struct QueuedCaInstance {
        id: u64,
        handle: CaInstanceHandle,
        parent_id: Option<u64>,
        discovered_from: Option<DiscoveredFrom>,
    }

    let mut next_id: u64 = 0;
    let mut queue: std::collections::VecDeque<QueuedCaInstance> = std::collections::VecDeque::new();
    for root in roots {
        queue.push_back(QueuedCaInstance {
            id: next_id,
            handle: root,
            parent_id: None,
            discovered_from: None,
        });
        next_id += 1;
    }

    let mut visited_manifest_uris: std::collections::HashSet<String> =
        std::collections::HashSet::new();
    let mut instances_processed = 0usize;
    let mut instances_failed = 0usize;
    let mut warnings: Vec<Warning> = Vec::new();
    let mut vrps: Vec<Vrp> = Vec::new();
    let mut aspas: Vec<AspaAttestation> = Vec::new();
    let mut router_keys: Vec<RouterKeyPayload> = Vec::new();
    let mut publication_points: Vec<PublicationPointAudit> = Vec::new();

    while let Some(node) = queue.pop_front() {
        let ca = &node.handle;
        if !visited_manifest_uris.insert(ca.manifest_rsync_uri.clone()) {
            continue;
        }

        if let Some(max_instances) = config.max_instances {
            if instances_processed >= max_instances {
                break;
            }
        }

        if let Some(max_depth) = config.max_depth {
            if ca.depth > max_depth {
                continue;
            }
        }

        let res = match runner.run_publication_point(ca) {
            Ok(v) => v,
            Err(e) => {
                instances_failed += 1;
                warnings.push(
                    Warning::new(format!("publication point failed: {e}"))
                        .with_context(&ca.manifest_rsync_uri),
                );
                continue;
            }
        };

        instances_processed += 1;
        warnings.extend(res.warnings);
        warnings.extend(res.objects.warnings);
        vrps.extend(res.objects.vrps);
        aspas.extend(res.objects.aspas);
        router_keys.extend(res.objects.router_keys);

        let mut audit = res.audit;
        audit.node_id = Some(node.id);
        audit.parent_node_id = node.parent_id;
        audit.discovered_from = node.discovered_from.clone();
        if config.compact_audit {
            audit.objects.clear();
            audit.warnings.clear();
        }
        publication_points.push(audit);

        let mut children = res.discovered_children;
        children.sort_by(|a, b| {
            a.handle
                .manifest_rsync_uri
                .cmp(&b.handle.manifest_rsync_uri)
                .then_with(|| {
                    a.discovered_from
                        .child_ca_certificate_rsync_uri
                        .cmp(&b.discovered_from.child_ca_certificate_rsync_uri)
                })
        });
        if let Err(e) = runner.prefetch_discovered_children(&children) {
            warnings.push(
                Warning::new(format!("prefetch discovered children failed: {e}"))
                    .with_context(&ca.manifest_rsync_uri),
            );
        }
        for child in children {
            queue.push_back(QueuedCaInstance {
                id: next_id,
                handle: child.handle.with_depth(ca.depth + 1),
                parent_id: Some(node.id),
                discovered_from: Some(child.discovered_from),
            });
            next_id += 1;
        }
    }

    Ok(TreeRunAuditOutput {
        tree: TreeRunOutput {
            instances_processed,
            instances_failed,
            warnings,
            vrps,
            aspas,
            router_keys,
        },
        publication_points,
    })
}

#[cfg(test)]
mod tests {
    use std::sync::{Arc, Mutex};

    use crate::audit::{DiscoveredFrom, PublicationPointAudit};
    use crate::validation::objects::{ObjectsOutput, ObjectsStats};

    use super::{
        CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult,
        PublicationPointRunner, TreeRunConfig, run_tree_serial_audit,
        run_tree_serial_audit_multi_root,
    };

    fn sample_handle(manifest: &str) -> CaInstanceHandle {
        CaInstanceHandle {
            depth: 0,
            tal_id: "arin".to_string(),
            parent_manifest_rsync_uri: None,
            ca_certificate_der: vec![1, 2, 3],
            ca_certificate_rsync_uri: None,
            effective_ip_resources: None,
            effective_as_resources: None,
            rsync_base_uri: "rsync://example.test/repo/".to_string(),
            manifest_rsync_uri: manifest.to_string(),
            publication_point_rsync_uri: "rsync://example.test/repo/".to_string(),
            rrdp_notification_uri: None,
        }
    }

    struct PrefetchRecordingRunner {
        seen_prefetch_children: Arc<Mutex<Vec<String>>>,
    }

    impl PublicationPointRunner for PrefetchRecordingRunner {
        fn prefetch_discovered_children(
            &self,
            children: &[DiscoveredChildCaInstance],
        ) -> Result<(), String> {
            let mut seen = self
                .seen_prefetch_children
                .lock()
                .expect("prefetch lock poisoned");
            seen.extend(
                children
                    .iter()
                    .map(|child| child.handle.manifest_rsync_uri.clone()),
            );
            Ok(())
        }

        fn run_publication_point(
            &self,
            ca: &CaInstanceHandle,
        ) -> Result<PublicationPointRunResult, String> {
            let children = if ca.manifest_rsync_uri.ends_with("root.mft") {
                vec![DiscoveredChildCaInstance {
                    handle: sample_handle("rsync://example.test/repo/child.mft"),
                    discovered_from: DiscoveredFrom {
                        parent_manifest_rsync_uri: ca.manifest_rsync_uri.clone(),
                        child_ca_certificate_rsync_uri: "rsync://example.test/repo/child.cer"
                            .to_string(),
                        child_ca_certificate_sha256_hex: "00".repeat(32),
                    },
                }]
            } else {
                Vec::new()
            };

            Ok(PublicationPointRunResult {
                source: crate::validation::manifest::PublicationPointSource::Fresh,
                snapshot: None,
                warnings: Vec::new(),
                objects: ObjectsOutput {
                    vrps: Vec::new(),
                    aspas: Vec::new(),
                    router_keys: Vec::new(),
                    local_outputs_cache: Vec::new(),
                    warnings: Vec::new(),
                    stats: ObjectsStats::default(),
                    audit: Vec::new(),
                },
                audit: PublicationPointAudit::default(),
                discovered_children: children,
            })
        }
    }

    #[test]
    fn run_tree_serial_audit_invokes_prefetch_hook_for_discovered_children() {
        let seen = Arc::new(Mutex::new(Vec::new()));
        let runner = PrefetchRecordingRunner {
            seen_prefetch_children: Arc::clone(&seen),
        };
        let out = run_tree_serial_audit(
            sample_handle("rsync://example.test/repo/root.mft"),
            &runner,
            &TreeRunConfig::default(),
        )
        .expect("tree run");
        assert_eq!(out.tree.instances_processed, 2);
        assert_eq!(
            seen.lock().expect("lock").as_slice(),
            ["rsync://example.test/repo/child.mft"]
        );
    }

    #[test]
    fn run_tree_serial_audit_multi_root_processes_all_roots() {
        let seen = Arc::new(Mutex::new(Vec::new()));
        let runner = PrefetchRecordingRunner {
            seen_prefetch_children: Arc::clone(&seen),
        };
        let out = run_tree_serial_audit_multi_root(
            vec![
                sample_handle("rsync://example.test/repo/root-a.mft"),
                sample_handle("rsync://example.test/repo/root-b.mft"),
            ],
            &runner,
            &TreeRunConfig::default(),
        )
        .expect("tree run");
        assert_eq!(out.tree.instances_processed, 2);
        assert_eq!(out.publication_points.len(), 2);
    }
}
