use crate::{
    axvisor::qemu_test::ShellAutoInitConfig,
    context::{arch_for_target_checked, target_for_arch_checked},
};

pub(crate) const ARCEOS_TEST_PACKAGES: &[&str] = &[
    "arceos-memtest",
    "arceos-exception",
    "arceos-affinity",
    "arceos-ipi",
    "arceos-net-echoserver",
    "arceos-net-httpclient",
    "arceos-net-httpserver",
    "arceos-irq",
    "arceos-parallel",
    "arceos-priority",
    "arceos-fs-shell",
    "arceos-sleep",
    "arceos-tls",
    "arceos-net-udpserver",
    "arceos-wait-queue",
    "arceos-yield",
];

const ARCEOS_TEST_TARGETS: &[&str] = &[
    "x86_64-unknown-none",
    "riscv64gc-unknown-none-elf",
    "aarch64-unknown-none-softfloat",
    "loongarch64-unknown-none-softfloat",
];
const ARCEOS_TEST_ARCHES: &[&str] = &["x86_64", "riscv64", "aarch64", "loongarch64"];

const AXVISOR_TEST_ARCHES: &[&str] = &["aarch64", "x86_64"];
const AXVISOR_AARCH64_TEST_SHELL_PREFIX: &str = "~ #";
const AXVISOR_AARCH64_TEST_SHELL_INIT_CMD: &str = "pwd && echo 'guest test pass!'";
const AXVISOR_AARCH64_TEST_SUCCESS_REGEX: &[&str] = &["(?m)^guest test pass!\\s*$"];
const AXVISOR_X86_64_TEST_SHELL_PREFIX: &str = ">>";
const AXVISOR_X86_64_TEST_SHELL_INIT_CMD: &str = "hello_world";
const AXVISOR_X86_64_TEST_SUCCESS_REGEX: &[&str] = &["Hello world from user mode program!"];
const AXVISOR_TEST_FAIL_REGEX: &[&str] = &[
    "(?i)\\bpanic(?:ked)?\\b",
    "(?i)kernel panic",
    "(?i)login incorrect",
    "(?i)permission denied",
];

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct AxvisorUbootBoardConfig {
    pub(crate) board: &'static str,
    pub(crate) guest: &'static str,
    pub(crate) build_config: &'static str,
    pub(crate) vmconfig: &'static str,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct AxvisorBoardTestGroup {
    pub(crate) name: &'static str,
    pub(crate) build_config: &'static str,
    pub(crate) vmconfigs: &'static [&'static str],
    pub(crate) board_test_config: &'static str,
}

const AXVISOR_UBOOT_BOARD_CONFIGS: &[AxvisorUbootBoardConfig] = &[
    AxvisorUbootBoardConfig {
        board: "orangepi-5-plus",
        guest: "linux",
        build_config: "os/axvisor/configs/board/orangepi-5-plus.toml",
        vmconfig: "os/axvisor/configs/vms/linux-aarch64-orangepi5p-smp1.toml",
    },
    AxvisorUbootBoardConfig {
        board: "phytiumpi",
        guest: "linux",
        build_config: "os/axvisor/configs/board/phytiumpi.toml",
        vmconfig: "os/axvisor/configs/vms/linux-aarch64-e2000-smp1.toml",
    },
    AxvisorUbootBoardConfig {
        board: "roc-rk3568-pc",
        guest: "linux",
        build_config: "os/axvisor/configs/board/roc-rk3568-pc.toml",
        vmconfig: "os/axvisor/configs/vms/linux-aarch64-rk3568-smp1.toml",
    },
];

const PHYTIUMPI_LINUX_VMCONFIGS: &[&str] =
    &["os/axvisor/configs/vms/linux-aarch64-e2000-smp1.toml"];
const ORANGEPI_5_PLUS_LINUX_VMCONFIGS: &[&str] =
    &["os/axvisor/configs/vms/linux-aarch64-orangepi5p-smp1.toml"];
const ROC_RK3568_PC_LINUX_VMCONFIGS: &[&str] =
    &["os/axvisor/configs/vms/linux-aarch64-rk3568-smp1.toml"];
const RDK_S100_LINUX_VMCONFIGS: &[&str] = &["os/axvisor/configs/vms/linux-aarch64-s100-smp1.toml"];

const AXVISOR_BOARD_TEST_GROUPS: &[AxvisorBoardTestGroup] = &[
    AxvisorBoardTestGroup {
        name: "phytiumpi-linux",
        build_config: "os/axvisor/configs/board/phytiumpi.toml",
        vmconfigs: PHYTIUMPI_LINUX_VMCONFIGS,
        board_test_config: "os/axvisor/configs/board-test/phytiumpi-linux.toml",
    },
    AxvisorBoardTestGroup {
        name: "orangepi-5-plus-linux",
        build_config: "os/axvisor/configs/board/orangepi-5-plus.toml",
        vmconfigs: ORANGEPI_5_PLUS_LINUX_VMCONFIGS,
        board_test_config: "os/axvisor/configs/board-test/orangepi-5-plus-linux.toml",
    },
    AxvisorBoardTestGroup {
        name: "roc-rk3568-pc-linux",
        build_config: "os/axvisor/configs/board/roc-rk3568-pc.toml",
        vmconfigs: ROC_RK3568_PC_LINUX_VMCONFIGS,
        board_test_config: "os/axvisor/configs/board-test/roc-rk3568-pc-linux.toml",
    },
    AxvisorBoardTestGroup {
        name: "rdk-s100-linux",
        build_config: "os/axvisor/configs/board/rdk-s100.toml",
        vmconfigs: RDK_S100_LINUX_VMCONFIGS,
        board_test_config: "os/axvisor/configs/board-test/rdk-s100-linux.toml",
    },
];

pub(crate) fn parse_arceos_test_target(target: &str) -> anyhow::Result<(&str, &str)> {
    parse_arch_or_target(
        target,
        "arceos qemu tests",
        ARCEOS_TEST_ARCHES,
        ARCEOS_TEST_TARGETS,
        target_for_arch_checked,
        arch_for_target_checked,
    )
}

pub(crate) fn parse_axvisor_test_target(target: &str) -> anyhow::Result<(&str, &str)> {
    parse_arch_or_target(
        target,
        "axvisor qemu tests",
        AXVISOR_TEST_ARCHES,
        &["aarch64-unknown-none-softfloat", "x86_64-unknown-none"],
        target_for_arch_checked,
        arch_for_target_checked,
    )
}

pub(crate) fn axvisor_uboot_board_config(
    board: &str,
    guest: &str,
) -> anyhow::Result<AxvisorUbootBoardConfig> {
    AXVISOR_UBOOT_BOARD_CONFIGS
        .iter()
        .copied()
        .find(|config| config.board == board && config.guest == guest)
        .ok_or_else(|| {
            anyhow!(
                "unsupported axvisor uboot test target board=`{}` guest=`{}`. Supported \
                 board/guest pairs are: {}",
                board,
                guest,
                supported_board_guest_pairs()
            )
        })
}

pub(crate) fn axvisor_board_test_groups(
    test_group: Option<&str>,
) -> anyhow::Result<Vec<AxvisorBoardTestGroup>> {
    match test_group {
        Some(name) => AXVISOR_BOARD_TEST_GROUPS
            .iter()
            .copied()
            .find(|group| group.name == name)
            .map(|group| vec![group])
            .ok_or_else(|| {
                anyhow!(
                    "unsupported axvisor board test group `{}`. Supported groups are: {}",
                    name,
                    supported_board_test_group_names()
                )
            }),
        None => Ok(AXVISOR_BOARD_TEST_GROUPS.to_vec()),
    }
}

fn default_axvisor_test_success_regex() -> Vec<String> {
    owned_patterns(AXVISOR_AARCH64_TEST_SUCCESS_REGEX)
}

fn default_axvisor_test_fail_regex() -> Vec<String> {
    owned_patterns(AXVISOR_TEST_FAIL_REGEX)
}

pub(crate) fn axvisor_test_shell_config(arch: &str) -> anyhow::Result<ShellAutoInitConfig> {
    match arch {
        "aarch64" => Ok(ShellAutoInitConfig {
            shell_prefix: AXVISOR_AARCH64_TEST_SHELL_PREFIX.to_string(),
            shell_init_cmd: AXVISOR_AARCH64_TEST_SHELL_INIT_CMD.to_string(),
            success_regex: default_axvisor_test_success_regex(),
            fail_regex: default_axvisor_test_fail_regex(),
        }),
        "x86_64" => Ok(ShellAutoInitConfig {
            shell_prefix: AXVISOR_X86_64_TEST_SHELL_PREFIX.to_string(),
            shell_init_cmd: AXVISOR_X86_64_TEST_SHELL_INIT_CMD.to_string(),
            success_regex: owned_patterns(AXVISOR_X86_64_TEST_SUCCESS_REGEX),
            fail_regex: default_axvisor_test_fail_regex(),
        }),
        _ => bail!(
            "unsupported target `{arch}` for axvisor qemu tests. Supported arch values are: {}",
            AXVISOR_TEST_ARCHES.join(", ")
        ),
    }
}

pub(crate) fn validate_supported_target(
    target: &str,
    suite_name: &str,
    supported_kind: &str,
    supported: &[&str],
) -> anyhow::Result<()> {
    if supported.contains(&target) {
        Ok(())
    } else {
        bail!(
            "unsupported target `{}` for {}. Supported {} are: {}",
            target,
            suite_name,
            supported_kind,
            supported.join(", ")
        )
    }
}

fn validate_supported_arch_or_target(
    value: &str,
    suite_name: &str,
    supported_arches: &[&str],
    supported_targets: &[&str],
) -> anyhow::Result<()> {
    if value.contains('-') {
        validate_supported_target(value, suite_name, "targets", supported_targets)
    } else {
        validate_supported_target(value, suite_name, "arch values", supported_arches)
    }
}

fn parse_arch_or_target<'a>(
    value: &'a str,
    suite_name: &str,
    supported_arches: &[&str],
    supported_targets: &[&str],
    resolve_target: fn(&str) -> anyhow::Result<&'static str>,
    resolve_arch: fn(&str) -> anyhow::Result<&'static str>,
) -> anyhow::Result<(&'a str, &'a str)> {
    validate_supported_arch_or_target(value, suite_name, supported_arches, supported_targets)?;
    if value.contains('-') {
        Ok((resolve_arch(value)?, value))
    } else {
        Ok((value, resolve_target(value)?))
    }
}

fn supported_board_guest_pairs() -> String {
    AXVISOR_UBOOT_BOARD_CONFIGS
        .iter()
        .map(|config| format!("{}/{}", config.board, config.guest))
        .collect::<Vec<_>>()
        .join(", ")
}

fn supported_board_test_group_names() -> String {
    AXVISOR_BOARD_TEST_GROUPS
        .iter()
        .map(|group| group.name)
        .collect::<Vec<_>>()
        .join(", ")
}

fn owned_patterns(patterns: &[&str]) -> Vec<String> {
    patterns
        .iter()
        .map(|pattern| (*pattern).to_string())
        .collect()
}

pub(crate) fn finalize_qemu_test_run(suite_name: &str, failed: &[String]) -> anyhow::Result<()> {
    if failed.is_empty() {
        println!("all {} qemu tests passed", suite_name);
        Ok(())
    } else {
        bail!(
            "{} qemu tests failed for {} package(s): {}",
            suite_name,
            failed.len(),
            failed.join(", ")
        )
    }
}

pub(crate) fn finalize_board_test_run(failed: &[String]) -> anyhow::Result<()> {
    if failed.is_empty() {
        println!("all axvisor board tests passed");
        Ok(())
    } else {
        bail!(
            "axvisor board tests failed for {} group(s): {}",
            failed.len(),
            failed.join(", ")
        )
    }
}

pub(crate) fn unsupported_uboot_test_command(os: &str) -> anyhow::Result<()> {
    bail!(
        "{os} does not support `test uboot` yet; only axvisor currently implements a U-Boot test \
         suite"
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn accepts_supported_arceos_targets() {
        assert_eq!(
            parse_arceos_test_target("x86_64-unknown-none").unwrap(),
            ("x86_64", "x86_64-unknown-none")
        );
        assert_eq!(
            parse_arceos_test_target("aarch64-unknown-none-softfloat").unwrap(),
            ("aarch64", "aarch64-unknown-none-softfloat")
        );
    }

    #[test]
    fn accepts_supported_arceos_arch_aliases() {
        assert_eq!(
            parse_arceos_test_target("x86_64").unwrap(),
            ("x86_64", "x86_64-unknown-none")
        );
        assert_eq!(
            parse_arceos_test_target("aarch64").unwrap(),
            ("aarch64", "aarch64-unknown-none-softfloat")
        );
    }

    #[test]
    fn rejects_unsupported_arceos_targets() {
        let err = parse_arceos_test_target("mips64-unknown-none").unwrap_err();

        assert!(
            err.to_string()
                .contains("unsupported target `mips64-unknown-none`")
        );
    }

    #[test]
    fn parses_supported_axvisor_arch_aliases() {
        assert_eq!(
            parse_axvisor_test_target("aarch64").unwrap(),
            ("aarch64", "aarch64-unknown-none-softfloat")
        );
        assert_eq!(
            parse_axvisor_test_target("x86_64").unwrap(),
            ("x86_64", "x86_64-unknown-none")
        );
    }

    #[test]
    fn accepts_axvisor_full_target_triples() {
        assert_eq!(
            parse_axvisor_test_target("aarch64-unknown-none-softfloat").unwrap(),
            ("aarch64", "aarch64-unknown-none-softfloat")
        );
    }

    #[test]
    fn rejects_unsupported_axvisor_arches() {
        let err = parse_axvisor_test_target("riscv64").unwrap_err();

        assert!(
            err.to_string()
                .contains("Supported arch values are: aarch64")
        );
    }

    #[test]
    fn parses_axvisor_uboot_board_config_for_linux_smoke() {
        assert_eq!(
            axvisor_uboot_board_config("orangepi-5-plus", "linux").unwrap(),
            AxvisorUbootBoardConfig {
                board: "orangepi-5-plus",
                guest: "linux",
                build_config: "os/axvisor/configs/board/orangepi-5-plus.toml",
                vmconfig: "os/axvisor/configs/vms/linux-aarch64-orangepi5p-smp1.toml",
            }
        );
        assert_eq!(
            axvisor_uboot_board_config("phytiumpi", "linux").unwrap(),
            AxvisorUbootBoardConfig {
                board: "phytiumpi",
                guest: "linux",
                build_config: "os/axvisor/configs/board/phytiumpi.toml",
                vmconfig: "os/axvisor/configs/vms/linux-aarch64-e2000-smp1.toml",
            }
        );
        assert_eq!(
            axvisor_uboot_board_config("roc-rk3568-pc", "linux").unwrap(),
            AxvisorUbootBoardConfig {
                board: "roc-rk3568-pc",
                guest: "linux",
                build_config: "os/axvisor/configs/board/roc-rk3568-pc.toml",
                vmconfig: "os/axvisor/configs/vms/linux-aarch64-rk3568-smp1.toml",
            }
        );
    }

    #[test]
    fn rejects_unsupported_axvisor_uboot_board() {
        let err = axvisor_uboot_board_config("unknown-board", "linux").unwrap_err();

        assert!(
            err.to_string()
                .contains("unsupported axvisor uboot test target board=`unknown-board`")
        );
        assert!(err.to_string().contains("orangepi-5-plus/linux"));
        assert!(err.to_string().contains("phytiumpi/linux"));
        assert!(err.to_string().contains("roc-rk3568-pc/linux"));
    }

    #[test]
    fn returns_all_axvisor_board_test_groups_when_no_filter_is_given() {
        let groups = axvisor_board_test_groups(None).unwrap();

        assert_eq!(
            groups.iter().map(|group| group.name).collect::<Vec<_>>(),
            vec![
                "phytiumpi-linux",
                "orangepi-5-plus-linux",
                "roc-rk3568-pc-linux",
                "rdk-s100-linux"
            ]
        );
    }

    #[test]
    fn filters_axvisor_board_test_group_by_name() {
        let groups = axvisor_board_test_groups(Some("phytiumpi-linux")).unwrap();

        assert_eq!(groups.len(), 1);
        assert_eq!(groups[0].name, "phytiumpi-linux");
        assert_eq!(
            groups[0].vmconfigs,
            &["os/axvisor/configs/vms/linux-aarch64-e2000-smp1.toml"]
        );
    }

    #[test]
    fn rejects_unknown_axvisor_board_test_group() {
        let err = axvisor_board_test_groups(Some("unknown-linux")).unwrap_err();

        assert!(
            err.to_string()
                .contains("unsupported axvisor board test group `unknown-linux`")
        );
        assert!(err.to_string().contains("phytiumpi-linux"));
        assert!(err.to_string().contains("orangepi-5-plus-linux"));
        assert!(err.to_string().contains("roc-rk3568-pc-linux"));
        assert!(err.to_string().contains("rdk-s100-linux"));
    }

    #[test]
    fn qemu_failure_summary_is_aggregated() {
        let err = finalize_qemu_test_run("arceos", &["pkg-b".to_string(), "pkg-c".to_string()])
            .unwrap_err();

        assert!(
            err.to_string()
                .contains("arceos qemu tests failed for 2 package(s): pkg-b, pkg-c")
        );
    }

    #[test]
    fn unsupported_uboot_error_is_explicit() {
        let err = unsupported_uboot_test_command("arceos").unwrap_err();

        assert!(
            err.to_string()
                .contains("arceos does not support `test uboot` yet")
        );
    }

    #[test]
    fn board_failure_summary_is_aggregated() {
        let err = finalize_board_test_run(&["phytiumpi-linux".to_string()]).unwrap_err();

        assert!(
            err.to_string()
                .contains("axvisor board tests failed for 1 group(s): phytiumpi-linux")
        );
    }
}
