use multiversx_sc::{abi::EndpointAbi, external_view_contract::EXTERNAL_VIEW_CONSTRUCTOR_FLAG};
use rustc_version::Version;
use std::{
    fs::{self, File},
    io::Write,
    path::PathBuf,
    str::FromStr,
};

use super::ContractVariant;

const PREFIX_AUTO_GENERATED: &str =
    "// Code generated by the multiversx-sc build system. DO NOT EDIT.

////////////////////////////////////////////////////
////////////////// AUTO-GENERATED //////////////////
////////////////////////////////////////////////////
";

const NUM_INIT: usize = 1;
const NUM_UPGRADE: usize = 1;
const NUM_ASYNC_CB: usize = 1;

const VER_1_71: &str = "1.71.0-nightly";
const FEATURES_PRE_RUSTC_1_71: &str = "
#![no_std]

// Configuration that works with rustc < 1.71.0.
// TODO: Recommended rustc version: 1.73.0 or newer.
#![feature(alloc_error_handler)]
";

const VER_1_73: &str = "1.73.0-nightly";
const FEATURES_PRE_RUSTC_1_73: &str = "
#![no_std]

// Configuration that works with rustc < 1.73.0.
// TODO: Recommended rustc version: 1.73.0 or newer.
";

const FEATURES_DEFAULT: &str = "
#![no_std]
";

impl ContractVariant {
    /// Makes sure that all the necessary wasm crate directories exist.
    pub fn create_wasm_crate_dir(&self) {
        fs::create_dir_all(PathBuf::from(&self.wasm_crate_path()).join("src")).unwrap();
    }

    fn allocator_macro_invocation(&self) -> String {
        format!(
            "multiversx_sc_wasm_adapter::allocator!({});",
            self.settings.allocator.to_allocator_macro_selector()
        )
    }

    fn panic_handler_macro_invocation(&self) -> &'static str {
        if self.settings.panic_message {
            "multiversx_sc_wasm_adapter::panic_handler_with_message!();"
        } else {
            "multiversx_sc_wasm_adapter::panic_handler!();"
        }
    }

    fn endpoint_macro_name(&self) -> &'static str {
        if self.settings.external_view {
            "multiversx_sc_wasm_adapter::external_view_endpoints!"
        } else {
            "multiversx_sc_wasm_adapter::endpoints!"
        }
    }

    /// Generates the wasm crate lib.rs source, st the given path.
    pub fn generate_wasm_src_lib_file(&self) {
        let lib_path = format!("{}/src/lib.rs", &self.wasm_crate_path());
        let mut wasm_lib_file = File::create(lib_path).unwrap();
        self.write_wasm_src_lib_contents(&mut wasm_lib_file);
    }

    fn write_wasm_src_lib_contents(&self, wasm_lib_file: &mut File) {
        writeln!(wasm_lib_file, "{PREFIX_AUTO_GENERATED}").unwrap();
        self.write_stat_comments(wasm_lib_file);
        write_prefix(wasm_lib_file);
        writeln!(wasm_lib_file, "{}", self.allocator_macro_invocation()).unwrap();
        writeln!(wasm_lib_file, "{}", self.panic_handler_macro_invocation()).unwrap();

        if self.settings.external_view {
            write_external_view_init(wasm_lib_file);
        }

        let contract_module_name = self.abi.get_crate_name_for_code();
        write_endpoints_macro(
            self.endpoint_macro_name(),
            wasm_lib_file,
            &contract_module_name,
            self.abi.iter_all_exports(),
        );

        write_async_callback_macro(wasm_lib_file, self.abi.has_callback, &contract_module_name);
    }
}

fn write_stat_comment(wasm_lib_file: &mut File, label: &str, number: usize) {
    writeln!(wasm_lib_file, "// {label:<35} {number:3}").unwrap();
}

impl ContractVariant {
    /// Writing some nicely formatted comments breaking down all exported functions.
    fn write_stat_comments(&self, wasm_lib_file: &mut File) {
        let mut total = self.abi.endpoints.len() + NUM_ASYNC_CB + self.abi.promise_callbacks.len();

        if !self.abi.constructors.is_empty() {
            write_stat_comment(wasm_lib_file, "Init:", NUM_INIT);
            total += NUM_INIT;
        }
        if !self.abi.upgrade_constructors.is_empty() {
            write_stat_comment(wasm_lib_file, "Upgrade:", NUM_UPGRADE);
            total += NUM_UPGRADE;
        }

        write_stat_comment(wasm_lib_file, "Endpoints:", self.abi.endpoints.len());
        if self.abi.has_callback {
            write_stat_comment(wasm_lib_file, "Async Callback:", NUM_ASYNC_CB);
        } else {
            write_stat_comment(wasm_lib_file, "Async Callback (empty):", NUM_ASYNC_CB);
        }
        if !self.abi.promise_callbacks.is_empty() {
            write_stat_comment(
                wasm_lib_file,
                "Promise callbacks:",
                self.abi.promise_callbacks.len(),
            );
        }

        write_stat_comment(wasm_lib_file, "Total number of exported functions:", total);
    }
}

fn select_features() -> &'static str {
    let meta = rustc_version::version_meta().unwrap();
    if meta.semver < Version::from_str(VER_1_71).unwrap() {
        FEATURES_PRE_RUSTC_1_71
    } else if meta.semver < Version::from_str(VER_1_73).unwrap() {
        FEATURES_PRE_RUSTC_1_73
    } else {
        FEATURES_DEFAULT
    }
}

fn write_prefix(wasm_lib_file: &mut File) {
    writeln!(wasm_lib_file, "{}", select_features()).unwrap();
}

fn write_endpoints_macro<'a, I>(
    full_macro_name: &str,
    wasm_lib_file: &mut File,
    contract_module_name: &str,
    endpoint_iter: I,
) where
    I: Iterator<Item = &'a EndpointAbi>,
{
    writeln!(wasm_lib_file).unwrap();
    writeln!(wasm_lib_file, "{full_macro_name} {{").unwrap();
    writeln!(wasm_lib_file, "    {contract_module_name}").unwrap();
    writeln!(wasm_lib_file, "    (").unwrap();
    for endpoint in endpoint_iter {
        if endpoint.rust_method_name == EXTERNAL_VIEW_CONSTRUCTOR_FLAG {
            continue;
        }
        writeln!(
            wasm_lib_file,
            "        {} => {}",
            endpoint.name, endpoint.rust_method_name
        )
        .unwrap();
    }
    writeln!(wasm_lib_file, "    )").unwrap();
    writeln!(wasm_lib_file, "}}").unwrap();
}

fn write_async_callback_macro(
    wasm_lib_file: &mut File,
    has_callback: bool,
    contract_module_name: &str,
) {
    writeln!(wasm_lib_file).unwrap();
    if has_callback {
        writeln!(
            wasm_lib_file,
            "multiversx_sc_wasm_adapter::async_callback! {{ {contract_module_name} }}"
        )
        .unwrap();
    } else {
        writeln!(
            wasm_lib_file,
            "multiversx_sc_wasm_adapter::async_callback_empty! {{}}",
        )
        .unwrap();
    }
}

fn write_external_view_init(wasm_lib_file: &mut File) {
    writeln!(wasm_lib_file).unwrap();
    writeln!(
        wasm_lib_file,
        "multiversx_sc_wasm_adapter::external_view_init! {{}}",
    )
    .unwrap();
}
