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>,
}

impl Default for TreeRunConfig {
    fn default() -> Self {
        Self {
            max_depth: None,
            max_instances: None,
        }
    }
}

#[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>;
}

#[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> {
    #[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();
    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.clone());
        warnings.extend(res.objects.warnings.clone());
        vrps.extend(res.objects.vrps.clone());
        aspas.extend(res.objects.aspas.clone());
        router_keys.extend(res.objects.router_keys.clone());

        let mut audit = res.audit.clone();
        audit.node_id = Some(node.id);
        audit.parent_node_id = node.parent_id;
        audit.discovered_from = node.discovered_from.clone();
        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)
                })
        });
        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,
    })
}
