use std::path::Path;
use std::path::PathBuf;

use crate::crd_pipeline_run::PipelineRun;
use crate::gitea_client::GiteaClient;
use crate::gitea_client::Tree;
use crate::remote_config::RemoteConfig;
use regex::Regex;
use tracing::debug;

pub(crate) async fn discover_webhook_bridge_config(
    gitea: &GiteaClient,
    repo_tree: &Tree,
) -> Result<RemoteConfig, Box<dyn std::error::Error>> {
    let remote_config_reference = repo_tree
        .files
        .iter()
        .find(|file_reference| file_reference.path == ".webhook_bridge/webhook_bridge.toml")
        .ok_or("File not found in remote repo: .webhook_bridge/webhook_bridge.toml.")?;

    let remote_config_contents =
        String::from_utf8(gitea.read_file(remote_config_reference).await?)?;
    let parsed_remote_config = RemoteConfig::from_str(remote_config_contents)?;

    Ok(parsed_remote_config)
}

pub(crate) async fn discover_matching_push_triggers<RE: AsRef<str>>(
    gitea: &GiteaClient,
    repo_tree: &Tree,
    git_ref: RE,
    remote_config: &RemoteConfig,
) -> Result<Vec<PipelineTemplate>, Box<dyn std::error::Error>> {
    let mut ret = Vec::new();
    let ref_to_branch_regex = Regex::new(r"refs/(heads|tags)/(?P<branch>.+)")?;
    let captures = ref_to_branch_regex
        .captures(git_ref.as_ref())
        .ok_or("Could not find branch name.")?;
    let branch = &captures["branch"];
    debug!("Detected branch from push as {:?}", branch);

    let push_triggers = remote_config.get_push_triggers_for_branch(branch)?;
    for trigger in push_triggers {
        let path_to_source = normalize_path(Path::new(".webhook_bridge").join(&trigger.source));
        let pipeline_template = repo_tree
            .files
            .iter()
            .find(|file_reference| Path::new(&file_reference.path) == path_to_source.as_path())
            .ok_or("Trigger source not found in remote repo.")?;
        let pipeline_contents = String::from_utf8(gitea.read_file(pipeline_template).await?)?;
        debug!("Pipeline template contents: {}", pipeline_contents);

        let pipeline: PipelineRun = serde_yaml::from_str(&pipeline_contents)?;
        ret.push(PipelineTemplate::new(
            trigger.name.clone(),
            trigger.clone_uri.clone(),
            pipeline,
        ));
    }

    Ok(ret)
}

fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
    let mut ret = PathBuf::new();

    for component in path.as_ref().components() {
        match component {
            // Prefix does not happen on unix-based systems.
            std::path::Component::Prefix(_)
            | std::path::Component::RootDir
            | std::path::Component::Normal(_) => {
                ret.push(component);
            }
            std::path::Component::CurDir => {}
            std::path::Component::ParentDir => {
                ret.pop();
            }
        }
    }

    ret
}

#[derive(Debug)]
pub(crate) struct PipelineTemplate {
    pub(crate) name: String,
    pub(crate) clone_uri: Option<String>,
    pub(crate) pipeline: PipelineRun,
}

impl PipelineTemplate {
    pub(crate) fn new<N: Into<String>, C: Into<Option<String>>>(
        name: N,
        clone_uri: C,
        pipeline: PipelineRun,
    ) -> PipelineTemplate {
        PipelineTemplate {
            name: name.into(),
            clone_uri: clone_uri.into(),
            pipeline,
        }
    }
}