use std::{path::PathBuf, str::FromStr};

use super::Provider;
use crate::nixpacks::{
    app::App,
    environment::Environment,
    nix::pkg::Pkg,
    plan::{
        phase::{Phase, StartPhase},
        BuildPlan,
    },
};
use anyhow::Result;
use path_slash::PathBufExt;

const COBOL_COMPILE_ARGS: &str = "COBOL_COMPILE_ARGS";
const COBOL_APP_NAME: &str = "COBOL_APP_NAME";
const DEFAULT_COBOL_COMPILE_ARGS: &str = "-x -o";

pub struct CobolProvider {}

impl Provider for CobolProvider {
    fn name(&self) -> &str {
        "cobol"
    }

    fn detect(&self, app: &App, _env: &Environment) -> Result<bool> {
        Ok(app.has_match("*.cbl"))
    }

    fn get_build_plan(
        &self,
        app: &App,
        environment: &Environment,
    ) -> anyhow::Result<Option<crate::nixpacks::plan::BuildPlan>> {
        let setup = Phase::setup(Some(vec![Pkg::new("gnu-cobol"), Pkg::new("gcc")]));

        let compile_args = environment
            .get_config_variable(COBOL_COMPILE_ARGS)
            .unwrap_or_else(|| DEFAULT_COBOL_COMPILE_ARGS.to_string());

        let app_path = CobolProvider::get_app_path(self, app, environment);

        let file_name = app_path
            .file_stem()
            .and_then(std::ffi::OsStr::to_str)
            .unwrap_or_default();

        let mut build = Phase::build(Some(format!(
            "cobc {} {} {}",
            compile_args,
            file_name,
            app_path.as_os_str().to_str().unwrap()
        )));
        build.depends_on_phase("setup");

        let start = StartPhase::new(format!("./{}", file_name));

        let plan = BuildPlan::new(&vec![setup, build], Some(start));
        Ok(Some(plan))
    }
}

impl CobolProvider {
    fn get_app_path(&self, app: &App, environment: &Environment) -> PathBuf {
        if let Some(app_name) = environment.get_config_variable(COBOL_APP_NAME) {
            if let Some(file_path) =
                CobolProvider::find_first_file(app, &format!("*{}.cbl", &app_name))
            {
                return file_path;
            }
        }

        if let Some(path) = CobolProvider::find_first_file(app, "*index.cbl") {
            return path;
        }
        if let Some(path) = CobolProvider::find_first_file(app, "*.cbl") {
            return path;
        }

        PathBuf::from("./")
    }

    fn find_first_file(app: &App, pattern: &str) -> Option<PathBuf> {
        app.find_files(pattern)
            .unwrap_or_default()
            .first()
            .map(|absolute_path| app.strip_source_path(absolute_path).unwrap_or_default())
            .and_then(|relative_path| CobolProvider::normalized_path(&relative_path))
    }

    fn normalized_path(path: &PathBuf) -> Option<PathBuf> {
        if let Some(normalized_path) = path.to_slash() {
            let path_string = PathBuf::from_str(normalized_path.to_string().as_str());

            if let Ok(path) = path_string {
                return Some(path);
            }
        }
        None
    }
}
