use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeDef;
use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline};
use clippy_utils::sym;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::source_map::Spanned;

use super::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS;

pub(super) fn check<'tcx>(
    cx: &LateContext<'tcx>,
    expr: &'tcx Expr<'_>,
    call_span: Span,
    recv: &'tcx Expr<'_>,
    arg: &'tcx Expr<'_>,
    msrv: Msrv,
) {
    if let ExprKind::MethodCall(path_segment, ..) = recv.kind
        && matches!(
            path_segment.ident.name,
            sym::to_lowercase | sym::to_uppercase | sym::to_ascii_lowercase | sym::to_ascii_uppercase
        )
    {
        return;
    }

    if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
        && cx.tcx.type_of(impl_id).instantiate_identity().is_str()
        && let ExprKind::Lit(Spanned {
            node: LitKind::Str(ext_literal, ..),
            ..
        }) = arg.kind
        && (2..=6).contains(&ext_literal.as_str().len())
        && let ext_str = ext_literal.as_str()
        && ext_str.starts_with('.')
        && (ext_str.chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit())
            || ext_str.chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit()))
        && !ext_str.chars().skip(1).all(|c| c.is_ascii_digit())
        && let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs()
        && (recv_ty.is_str() || recv_ty.is_lang_item(cx, LangItem::String))
    {
        span_lint_and_then(
            cx,
            CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
            recv.span.to(call_span),
            "case-sensitive file extension comparison",
            |diag| {
                diag.help("consider using a case-insensitive comparison instead");
                if let Some(recv_source) = recv.span.get_source_text(cx) {
                    let recv_source = if cx.typeck_results().expr_ty(recv).is_ref() {
                        recv_source.to_owned()
                    } else {
                        format!("&{recv_source}")
                    };

                    let suggestion_source = reindent_multiline(
                        &format!(
                            "std::path::Path::new({recv_source})
                                .extension()
                                .{}|ext| ext.eq_ignore_ascii_case(\"{}\"))",
                            if msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) {
                                "is_some_and("
                            } else {
                                "map_or(false, "
                            },
                            ext_str.strip_prefix('.').unwrap(),
                        ),
                        true,
                        Some(indent_of(cx, call_span).unwrap_or(0) + 4),
                    );

                    diag.span_suggestion(
                        recv.span.to(call_span),
                        "use std::path::Path",
                        suggestion_source,
                        Applicability::MaybeIncorrect,
                    );
                }
            },
        );
    }
}
