use std::collections::BTreeSet;

use crate::ast::{Expr, FnDef, Literal, Pattern, TopLevel};
use crate::call_graph::find_recursive_fns;
use crate::types::Type;
use crate::types::parse_type_str_strict;

use super::{
    CheckFinding, collect_target_call_args, constructor_tag_from_expr, expr_is_bool_case,
    expr_is_empty_list_case, expr_is_empty_string_case, expr_is_int_literal_case,
    expr_is_non_empty_list_case, expr_is_option_none_case, expr_is_option_some_case,
    expr_is_result_err_case, expr_is_result_ok_case, local_sum_type_constructors,
    merge_verify_blocks, module_name_for_items, verify_cases_block_is_well_formed,
};

fn direct_match_target(f: &FnDef) -> Option<(usize, &[crate::ast::MatchArm])> {
    let Expr::Match { subject, arms, .. } = f.body.tail_expr()? else {
        return None;
    };
    let Expr::Ident(subject_name) = subject.as_ref() else {
        return None;
    };
    let param_index = f.params.iter().position(|(name, _)| name == subject_name)?;
    Some((param_index, arms.as_slice()))
}

fn enum_match_coverage_target(f: &FnDef) -> Option<(usize, Vec<String>)> {
    let (param_index, arms) = direct_match_target(f)?;
    let constructors: BTreeSet<String> = arms
        .iter()
        .filter_map(|arm| super::constructor_tag_from_pattern(&arm.pattern))
        .collect();
    if constructors.is_empty() {
        None
    } else {
        Some((param_index, constructors.into_iter().collect()))
    }
}

fn bool_match_coverage_target(f: &FnDef) -> Option<usize> {
    let (param_index, arms) = direct_match_target(f)?;
    let (_, param_ty) = f.params.get(param_index)?;
    let Ok(Type::Bool) = parse_type_str_strict(param_ty) else {
        return None;
    };
    arms.iter()
        .any(|arm| matches!(arm.pattern, Pattern::Literal(Literal::Bool(_))))
        .then_some(param_index)
}

fn list_match_coverage_target(f: &FnDef) -> Option<usize> {
    let (param_index, arms) = direct_match_target(f)?;
    let (_, param_ty) = f.params.get(param_index)?;
    let Ok(Type::List(_)) = parse_type_str_strict(param_ty) else {
        return None;
    };
    arms.iter()
        .any(|arm| matches!(arm.pattern, Pattern::EmptyList | Pattern::Cons(_, _)))
        .then_some(param_index)
}

pub fn collect_verify_coverage_warnings(items: &[TopLevel]) -> Vec<CheckFinding> {
    collect_verify_coverage_warnings_in(items, None)
}

pub fn collect_verify_coverage_warnings_in(
    items: &[TopLevel],
    source_file: Option<&str>,
) -> Vec<CheckFinding> {
    let module_name = module_name_for_items(items);
    let recursive_fns = find_recursive_fns(items);
    let fn_defs: std::collections::HashMap<&str, &FnDef> = items
        .iter()
        .filter_map(|item| {
            if let TopLevel::FnDef(f) = item {
                Some((f.name.as_str(), f))
            } else {
                None
            }
        })
        .collect();

    let mut warnings = Vec::new();
    for block in merge_verify_blocks(items) {
        if !verify_cases_block_is_well_formed(&block) {
            continue;
        }
        let Some(f) = fn_defs.get(block.fn_name.as_str()).copied() else {
            continue;
        };

        if let Ok(ret_ty) = parse_type_str_strict(&f.return_type) {
            if matches!(ret_ty, Type::Result(_, _))
                && !block
                    .cases
                    .iter()
                    .any(|(_, right)| expr_is_result_ok_case(right))
            {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not include any Result.Ok case",
                        block.fn_name
                    ),
                });
            }

            if matches!(ret_ty, Type::Result(_, _))
                && !block
                    .cases
                    .iter()
                    .any(|(_, right)| expr_is_result_err_case(right))
            {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not include any Result.Err case",
                        block.fn_name
                    ),
                });
            }

            if matches!(ret_ty, Type::Option(_))
                && !block
                    .cases
                    .iter()
                    .any(|(_, right)| expr_is_option_some_case(right))
            {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not include any Option.Some case",
                        block.fn_name
                    ),
                });
            }

            if matches!(ret_ty, Type::Option(_))
                && !block
                    .cases
                    .iter()
                    .any(|(_, right)| expr_is_option_none_case(right))
            {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not include any Option.None case",
                        block.fn_name
                    ),
                });
            }

            if matches!(ret_ty, Type::Bool)
                && !block
                    .cases
                    .iter()
                    .any(|(_, right)| expr_is_bool_case(right, true))
            {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not include any `true` result",
                        block.fn_name
                    ),
                });
            }

            if matches!(ret_ty, Type::Bool)
                && !block
                    .cases
                    .iter()
                    .any(|(_, right)| expr_is_bool_case(right, false))
            {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not include any `false` result",
                        block.fn_name
                    ),
                });
            }

            if let Type::Named(type_name) = ret_ty
                && let Some(constructors) = local_sum_type_constructors(items, &type_name)
            {
                let mut covered = BTreeSet::new();
                for (_, right) in &block.cases {
                    if let Some(tag) = constructor_tag_from_expr(right)
                        && constructors.contains(&tag)
                    {
                        covered.insert(tag);
                    }
                }
                if covered.len() < constructors.len() {
                    warnings.push(CheckFinding {
                        line: block.line,
                        module: module_name.clone(),
                        file: source_file.map(|s| s.to_string()),
                        message: format!(
                            "verify examples for {} cover {}/{} output constructors",
                            block.fn_name,
                            covered.len(),
                            constructors.len()
                        ),
                    });
                }
            }
        }

        if let Some((param_index, constructors)) = enum_match_coverage_target(f) {
            let mut covered = BTreeSet::new();
            for (left, _) in &block.cases {
                let mut args = Vec::new();
                collect_target_call_args(left, &block.fn_name, param_index, &mut args);
                for arg in args {
                    if let Some(tag) = constructor_tag_from_expr(arg)
                        && constructors.contains(&tag)
                    {
                        covered.insert(tag);
                    }
                }
            }
            if covered.len() < constructors.len() {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} cover {}/{} enum constructors",
                        block.fn_name,
                        covered.len(),
                        constructors.len()
                    ),
                });
            }
        }

        if let Some(param_index) = bool_match_coverage_target(f) {
            let param_name = &f.params[param_index].0;
            let mut args = Vec::new();
            for (left, _) in &block.cases {
                collect_target_call_args(left, &block.fn_name, param_index, &mut args);
            }
            if !args.iter().any(|arg| expr_is_bool_case(arg, true)) {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not cover `{}` = `true`",
                        block.fn_name, param_name
                    ),
                });
            }
            if !args.iter().any(|arg| expr_is_bool_case(arg, false)) {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not cover `{}` = `false`",
                        block.fn_name, param_name
                    ),
                });
            }
        }

        if let Some(param_index) = list_match_coverage_target(f) {
            let param_name = &f.params[param_index].0;
            let mut args = Vec::new();
            for (left, _) in &block.cases {
                collect_target_call_args(left, &block.fn_name, param_index, &mut args);
            }
            if !args.iter().any(|arg| expr_is_empty_list_case(arg)) {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not cover empty list input for `{}`",
                        block.fn_name, param_name
                    ),
                });
            }
            if !args.iter().any(|arg| expr_is_non_empty_list_case(arg)) {
                warnings.push(CheckFinding {
                    line: block.line,
                    module: module_name.clone(),
                    file: source_file.map(|s| s.to_string()),
                    message: format!(
                        "verify examples for {} do not cover non-empty list input for `{}`",
                        block.fn_name, param_name
                    ),
                });
            }
        }

        if recursive_fns.contains(&block.fn_name) && f.params.len() == 1 {
            let (param_name, param_ty) = &f.params[0];
            let Ok(param_ty) = parse_type_str_strict(param_ty) else {
                continue;
            };
            let mut args = Vec::new();
            for (left, _) in &block.cases {
                collect_target_call_args(left, &block.fn_name, 0, &mut args);
            }

            match param_ty {
                Type::Int => {
                    let has_zero = args.iter().any(|arg| expr_is_int_literal_case(arg, 0));
                    let has_one = args.iter().any(|arg| expr_is_int_literal_case(arg, 1));
                    if !has_zero && !has_one {
                        warnings.push(CheckFinding {
                                line: block.line,
                                module: module_name.clone(),
                                file: source_file.map(|s| s.to_string()),
                                message: format!(
                                    "verify examples for recursive function {} may not include a numeric base-case input for `{}` (`0` or `1`)",
                                    block.fn_name, param_name
                                ),
                            });
                    }
                }
                Type::List(_) => {
                    if !args.iter().any(|arg| expr_is_empty_list_case(arg)) {
                        warnings.push(CheckFinding {
                                line: block.line,
                                module: module_name.clone(),
                                file: source_file.map(|s| s.to_string()),
                                message: format!(
                                    "verify examples for recursive function {} may not include an empty list input for `{}`",
                                    block.fn_name, param_name
                                ),
                            });
                    }
                }
                Type::Str => {
                    if !args.iter().any(|arg| expr_is_empty_string_case(arg)) {
                        warnings.push(CheckFinding {
                                line: block.line,
                                module: module_name.clone(),
                                file: source_file.map(|s| s.to_string()),
                                message: format!(
                                    "verify examples for recursive function {} may not include an empty string input for `{}`",
                                    block.fn_name, param_name
                                ),
                            });
                    }
                }
                _ => {}
            }
        }

        let parser_like = f.name.starts_with("parse") || f.name == "fromString";
        if parser_like {
            let Ok(ret_ty) = parse_type_str_strict(&f.return_type) else {
                continue;
            };
            if matches!(ret_ty, Type::Result(_, _) | Type::Option(_))
                && let Some((param_name, param_ty)) = f.params.first()
            {
                let Ok(Type::Str) = parse_type_str_strict(param_ty) else {
                    continue;
                };
                let mut args = Vec::new();
                for (left, _) in &block.cases {
                    collect_target_call_args(left, &block.fn_name, 0, &mut args);
                }
                if !args.iter().any(|arg| expr_is_empty_string_case(arg)) {
                    warnings.push(CheckFinding {
                        line: block.line,
                        module: module_name.clone(),
                        file: source_file.map(|s| s.to_string()),
                        message: format!(
                            "verify examples for {} may not include an empty string input for `{}`",
                            block.fn_name, param_name
                        ),
                    });
                }
            }
        }
    }

    warnings
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::lexer::Lexer;
    use crate::parser::Parser;

    fn parse_items(src: &str) -> Vec<TopLevel> {
        let mut lexer = Lexer::new(src);
        let tokens = lexer.tokenize().expect("lex failed");
        let mut parser = Parser::new(tokens);
        parser.parse().expect("parse failed")
    }

    #[test]
    fn coverage_warns_when_result_verify_has_no_err_example() {
        let items = parse_items(
            r#"
fn mayFail(n: Int) -> Result<Int, String>
    match n
        0 -> Result.Err("zero")
        _ -> Result.Ok(n)

verify mayFail
    mayFail(1) => Result.Ok(1)
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(|w| {
                w.message == "verify examples for mayFail do not include any Result.Err case"
            }),
            "expected missing-err warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_warns_when_result_verify_has_no_ok_example() {
        let items = parse_items(
            r#"
fn alwaysFail(n: Int) -> Result<Int, String>
    Result.Err("nope")

verify alwaysFail
    alwaysFail(1) => Result.Err("nope")
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(|w| {
                w.message == "verify examples for alwaysFail do not include any Result.Ok case"
            }),
            "expected missing-ok warning, got {:?}",
            warnings
        );
        assert!(
            warnings.iter().any(|w| w.line == 5),
            "expected verify-block line number, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_warns_when_option_verify_has_no_none_example() {
        let items = parse_items(
            r#"
fn maybe(n: Int) -> Option<Int>
    match n
        0 -> Option.None
        _ -> Option.Some(n)

verify maybe
    maybe(1) => Option.Some(1)
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(|w| {
                w.message == "verify examples for maybe do not include any Option.None case"
            }),
            "expected missing-none warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_warns_when_option_verify_has_no_some_example() {
        let items = parse_items(
            r#"
fn nope(n: Int) -> Option<Int>
    Option.None

verify nope
    nope(1) => Option.None
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(|w| {
                w.message == "verify examples for nope do not include any Option.Some case"
            }),
            "expected missing-some warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_warns_when_bool_verify_has_only_one_result_shape() {
        let items = parse_items(
            r#"
fn isZero(n: Int) -> Bool
    n == 0

verify isZero
    isZero(0) => true
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(
                |w| w.message == "verify examples for isZero do not include any `false` result"
            ),
            "expected missing-false warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_warns_when_sum_output_constructors_are_partial() {
        let items = parse_items(
            r#"
type Outcome
    Win(String)
    Lose(String)
    Continue

fn classify(n: Int) -> Outcome
    match n
        0 -> Outcome.Continue
        1 -> Outcome.Win("yay")
        _ -> Outcome.Lose("no")

verify classify
    classify(0) => Outcome.Continue
    classify(1) => Outcome.Win("yay")
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(|w| {
                w.message == "verify examples for classify cover 2/3 output constructors"
            }),
            "expected output-constructor coverage warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_warns_when_enum_match_examples_cover_subset_of_constructors() {
        let items = parse_items(
            r#"
type Input
    Help
    Exit
    Echo(String)

fn dispatch(input: Input) -> String
    match input
        Input.Help -> "help"
        Input.Exit -> "exit"
        Input.Echo(value) -> value

verify dispatch
    dispatch(Input.Help) => "help"
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings
                .iter()
                .any(|w| w.message == "verify examples for dispatch cover 1/3 enum constructors"),
            "expected enum-coverage warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_warns_when_bool_match_input_examples_miss_a_branch() {
        let items = parse_items(
            r#"
fn flagName(flag: Bool) -> String
    match flag
        true -> "yes"
        false -> "no"

verify flagName
    flagName(true) => "yes"
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings
                .iter()
                .any(|w| w.message == "verify examples for flagName do not cover `flag` = `false`"),
            "expected bool-input coverage warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_warns_when_list_match_input_examples_miss_empty_case() {
        let items = parse_items(
            r#"
fn headOrZero(xs: List<Int>) -> Int
    match xs
        [] -> 0
        [head, ..tail] -> head

verify headOrZero
    headOrZero([1, 2]) => 1
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(|w| {
                w.message == "verify examples for headOrZero do not cover empty list input for `xs`"
            }),
            "expected empty-list coverage warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_merges_multiple_verify_blocks_before_checking_shapes() {
        let items = parse_items(
            r#"
fn maybe(n: Int) -> Option<Int>
    match n
        0 -> Option.None
        _ -> Option.Some(n)

verify maybe
    maybe(0) => Option.None

verify maybe
    maybe(1) => Option.Some(1)
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            !warnings.iter().any(|w| w.message.contains("Option.None"))
                && !warnings.iter().any(|w| w.message.contains("Option.Some")),
            "expected merged verify blocks to satisfy coverage, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_soft_warns_when_recursive_int_fn_has_no_zero_or_one_example() {
        let items = parse_items(
            r#"
fn fib(n: Int) -> Int
    match n == 0
        true -> 0
        false -> match n == 1
            true -> 1
            false -> fib(n - 1) + fib(n - 2)

verify fib
    fib(5) => 5
    fib(6) => 8
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(|w| {
                w.message == "verify examples for recursive function fib may not include a numeric base-case input for `n` (`0` or `1`)"
            }),
            "expected recursive boundary warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_soft_warns_when_recursive_list_fn_has_no_empty_input_example() {
        let items = parse_items(
            r#"
fn sum(xs: List<Int>) -> Int
    match xs
        [] -> 0
        [head, ..tail] -> head + sum(tail)

verify sum
    sum([1, 2, 3]) => 6
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(|w| {
                w.message
                    == "verify examples for recursive function sum may not include an empty list input for `xs`"
            }),
            "expected recursive empty-list warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_soft_warns_when_parser_like_fn_has_no_empty_string_example() {
        let items = parse_items(
            r#"
fn fromString(s: String) -> Result<Int, String>
    match s == ""
        true -> Result.Err("empty")
        false -> Result.Ok(1)

verify fromString
    fromString("42") => Result.Ok(1)
    fromString("x") => Result.Err("empty")
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            warnings.iter().any(|w| {
                w.message
                    == "verify examples for fromString may not include an empty string input for `s`"
            }),
            "expected parser empty-string warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_soft_does_not_warn_when_recursive_base_case_is_split_across_verify_blocks() {
        let items = parse_items(
            r#"
fn fib(n: Int) -> Int
    match n == 0
        true -> 0
        false -> match n == 1
            true -> 1
            false -> fib(n - 1) + fib(n - 2)

verify fib
    fib(0) => 0

verify fib
    fib(5) => 5
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(
            !warnings
                .iter()
                .any(|w| w.message.contains("numeric base-case input")),
            "expected merged verify blocks to silence recursive boundary warning, got {:?}",
            warnings
        );
    }

    #[test]
    fn coverage_does_not_warn_when_enum_and_output_examples_are_present() {
        let items = parse_items(
            r#"
type Input
    Help
    Exit

fn run(input: Input) -> Result<String, String>
    match input
        Input.Help -> Result.Ok("help")
        Input.Exit -> Result.Err("exit")

verify run
    run(Input.Help) => Result.Ok("help")
    run(Input.Exit) => Result.Err("exit")
"#,
        );
        let warnings = collect_verify_coverage_warnings(&items);
        assert!(warnings.is_empty(), "unexpected warnings: {:?}", warnings);
    }
}
