extern crate alloc;

use std::collections::HashMap;
use std::fs;

use beancount_importers_generators::compute_checksum_for_files;
use beancount_importers_generators::install_tracing;
use beancount_importers_generators::load_manifest;
use camino::Utf8Path;
use hard_xml::XmlRead;
use miette::IntoDiagnostic as _;
use miette::Result;
use toml_edit::Item;

use crate::passes::codegen;
use crate::passes::dependency_analysis::DependencyGraph;
use crate::passes::type_resolution::resolve_types;
use crate::passes::type_resolution::Type;
use crate::types::cooked;
use crate::types::external_code_sets::ExternalCodeSets;
use crate::types::raw::Repository;
use beancount_importers_generators::save_manifest;

mod passes;
mod types;

pub fn main() -> Result<()> {
    install_tracing();

    let (manifest_path, mut manifest) = load_manifest()?;

    let sources = &mut manifest["package"]["metadata"]["px"]["source"];

    let package_directory = manifest_path.parent().expect("absolute path");

    run(sources, package_directory)?;

    save_manifest(manifest)
}

fn run(sources: &mut Item, package_directory: &Utf8Path) -> Result<()> {
    let repository_path = package_directory.join("20220520_ISO20022_2013_eRepository.iso20022");
    let codeset_path = package_directory.join("4Q2022_ExternalCodeSets_v1.json");

    let current_checksum = compute_checksum_for_files(&[&repository_path, &codeset_path])?;
    let old_checksum = &mut sources["checksum"];
    if let Some(old_checksum) = old_checksum.as_str() {
        if old_checksum == current_checksum {
            return Ok(());
        }
    }

    let roots: &[&str] = &[
        "_II5S49E-Ed-BzquC8wXy7w_620541391",
        "_eYI_SW2PEei3KuUgpx7Xcw",
    ];
    let type_overrides = maplit::hashmap! {
        "_bqIp6tp-Ed-ak6NoX_4Aeg_1823330336" => Type { name: "CurrencyCode", needs_lifetime: false },
        "_bqIp5tp-Ed-ak6NoX_4Aeg_-1326801359" => Type { name: "CurrencyCode", needs_lifetime: false },
        "_YWr8K9p-Ed-ak6NoX_4Aeg_1527093628" => Type { name: "BicIdentifier", needs_lifetime: true },
        "_YYB_-9p-Ed-ak6NoX_4Aeg_1297781972" => Type { name: "BicIdentifier", needs_lifetime: true },
        "_jp-90kI6EeirV6K70JJQ8Q" => Type { name: "BicIdentifier", needs_lifetime: true },
        "_gnK3okI7EeirV6K70JJQ8Q" => Type { name: "BicIdentifier", needs_lifetime: true },
        "_bTfEctp-Ed-ak6NoX_4Aeg_-804722522" => Type { name: "CountryCode", needs_lifetime: false },
        "_YYxm0dp-Ed-ak6NoX_4Aeg_1226525818" => Type { name: "IbanIdentifier", needs_lifetime: false },
        "_YYU64Np-Ed-ak6NoX_4Aeg_-1113170580" => Type { name: "IsinIdentifier", needs_lifetime: false },
        "_TUmCIAEcEeCQm6a_G2yO_w_637447613" => Type { name: "IsoLanguageCode", needs_lifetime: false },
        "_rMvjUYIXEeWE0I8iABxEQA" => Type { name: "IsinIdentifier", needs_lifetime: false },
        "_h7Yn9yjAEeKnA5P_jl2DUw" => Type { name: "LeiIdentifier", needs_lifetime: false },
        "_TQIBwHrVEeidVZmeoasaWQ" => Type { name: "UuidV4Identifier", needs_lifetime: false },
    };

    let source_directory = package_directory.join("src");
    generate_external_code_sets(&codeset_path, {
        let mut path = source_directory.join("types");
        path.push("external_code_sets.rs");
        path
    })?;

    generate_erepository(
        &repository_path,
        roots,
        type_overrides,
        source_directory.join("types.rs"),
    )?;

    *old_checksum = toml_edit::value(current_checksum);

    Ok(())
}

fn generate_external_code_sets(
    codeset_path: impl AsRef<Utf8Path>,
    output_path: impl AsRef<Utf8Path>,
) -> Result<()> {
    let external_code_sets = fs::read_to_string(codeset_path.as_ref()).into_diagnostic()?;
    let external_code_sets: ExternalCodeSets =
        serde_json::de::from_str(&external_code_sets).into_diagnostic()?;

    let code = codegen::generate_external_code_sets(&external_code_sets.definitions);

    fs::write(output_path.as_ref(), code.to_string()).into_diagnostic()?;

    Ok(())
}

fn generate_erepository(
    repository_path: impl AsRef<Utf8Path>,
    roots: &[&str],
    manual_types: HashMap<&str, Type>,
    output_path: impl AsRef<Utf8Path>,
) -> Result<()> {
    let repo = fs::read_to_string(repository_path.as_ref()).into_diagnostic()?;
    let repo = Repository::from_str(&repo).into_diagnostic()?;
    let definitions = cooked::cook(repo);

    let graph = DependencyGraph::construct_for(&definitions, roots);

    let types = resolve_types(&definitions, &graph, &manual_types);

    let code = codegen::generate_erepository(&definitions, &graph, &manual_types, &types);

    fs::write(output_path.as_ref(), code.to_string()).into_diagnostic()?;

    Ok(())
}