use std::path::{Path, PathBuf};

use anyhow::Result;

use crate::model::Pipeline;
use crate::parser;

use super::{ResolveContext, resolve_pipeline, resolve_pipeline_tolerant};

/// Parse and resolve a child pipeline file, applying force overrides
/// and respecting the tolerant/strict mode from the context.
pub fn resolve_child_pipeline(child_path: &Path, ctx: &ResolveContext) -> Result<Pipeline> {
    let project_root = ctx.project_root.or_else(|| child_path.parent());
    let raw = parser::parse_pipeline_file_with_root(
        child_path,
        ctx.gitlab_config,
        project_root,
        ctx.allow_unresolved,
    )?;
    if ctx.allow_unresolved {
        resolve_pipeline_tolerant(raw, ctx.git_context, ctx.var_overrides)
    } else {
        resolve_pipeline(raw, ctx.git_context, ctx.var_overrides)
    }
}

/// Resolve a job query that wasn't found in the root pipeline by searching
/// child pipelines (reached via `trigger: include: local:` jobs).
///
/// Supports two forms:
///   - `trigger_name/child_job` — explicit trigger job + child job
///   - `child_job` — auto-search across all child pipelines
///
/// Returns the parsed child pipeline, the resolved job name within it,
/// and the path to the child pipeline file.
pub fn resolve_child_job(
    query: &str,
    root_pipeline: &Pipeline,
    root_file_path: &Path,
    ctx: &ResolveContext,
) -> Result<(Pipeline, String, PathBuf)> {
    let root_dir = ctx
        .project_root
        .or_else(|| root_file_path.parent())
        .ok_or_else(|| anyhow::anyhow!("Cannot determine project root directory"))?;

    if let Some((trigger_query, child_query)) = query.split_once('/') {
        let trigger_name = root_pipeline
            .find_job(trigger_query)
            .map_err(|e| anyhow::anyhow!("{}", e))?;
        let trigger_job = &root_pipeline.jobs[trigger_name];
        let trigger_file = trigger_job.trigger_file.as_ref().ok_or_else(|| {
            anyhow::anyhow!(
                "Job '{}' is not a trigger job (no trigger: include: local:)",
                trigger_name
            )
        })?;
        let child_path = root_dir.join(trigger_file.trim_start_matches('/'));
        if !child_path.exists() {
            anyhow::bail!(
                "Trigger job '{}' references child pipeline '{}' which does not exist",
                trigger_name,
                child_path.display()
            );
        }
        let child_pipeline = resolve_child_pipeline(&child_path, ctx)?;
        let job_name = child_pipeline
            .find_job(child_query)
            .map_err(|e| anyhow::anyhow!("{}", e))?
            .to_string();
        return Ok((child_pipeline, job_name, child_path));
    }

    // Auto-search: iterate all trigger jobs and search their child pipelines
    let mut matches: Vec<(String, Pipeline, String, PathBuf)> = Vec::new();
    for (name, job) in &root_pipeline.jobs {
        let Some(trigger_file) = &job.trigger_file else {
            continue;
        };
        let child_path = root_dir.join(trigger_file.trim_start_matches('/'));
        if !child_path.exists() {
            continue;
        }
        let child_pipeline = match resolve_child_pipeline(&child_path, ctx) {
            Ok(p) => p,
            Err(_) => continue,
        };
        if let Ok(job_name) = child_pipeline.find_job(query) {
            let job_name = job_name.to_string();
            matches.push((name.clone(), child_pipeline, job_name, child_path));
        }
    }

    match matches.len() {
        0 => {
            let trigger_jobs: Vec<&str> = root_pipeline
                .jobs
                .iter()
                .filter(|(_, j)| j.trigger_file.is_some())
                .map(|(n, _)| n.as_str())
                .collect();
            if trigger_jobs.is_empty() {
                anyhow::bail!("Job '{}' not found (no child pipelines to search)", query);
            }
            anyhow::bail!(
                "Job '{}' not found in root pipeline or child pipelines (searched: {})",
                query,
                trigger_jobs.join(", ")
            );
        }
        1 => {
            let (_, pipeline, job_name, child_path) = matches.into_iter().next().unwrap();
            Ok((pipeline, job_name, child_path))
        }
        _ => {
            let descriptions: Vec<String> = matches
                .iter()
                .map(|(trigger, _, job, _)| format!("  {}/{}", trigger, job))
                .collect();
            anyhow::bail!(
                "Ambiguous job '{}' found in multiple child pipelines. Use explicit syntax:\n{}",
                query,
                descriptions.join("\n")
            );
        }
    }
}
