use anyhow::Result;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{
    self as ast, Expr, ExprBinOp, ExprContext, ExprNoneLiteral, Operator, PythonVersion,
    helpers::{pep_604_union, typing_optional},
    name::Name,
    operator_precedence::OperatorPrecedence,
    token::{Tokens, parenthesized_range},
};
use ruff_python_semantic::analyze::typing::{traverse_literal, traverse_union};
use ruff_text_size::{Ranged, TextRange};

use smallvec::SmallVec;

use crate::checkers::ast::Checker;
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};

/// ## What it does
/// Checks for redundant `Literal[None]` annotations.
///
/// ## Why is this bad?
/// While `Literal[None]` is a valid type annotation, it is semantically equivalent to `None`.
/// Prefer `None` over `Literal[None]` for both consistency and readability.
///
/// ## Example
/// ```python
/// from typing import Literal
///
/// Literal[None]
/// Literal[1, 2, 3, "foo", 5, None]
/// ```
///
/// Use instead:
/// ```python
/// from typing import Literal
///
/// None
/// Literal[1, 2, 3, "foo", 5] | None
/// ```
///
/// ## Fix safety and availability
/// This rule's fix is marked as safe unless the literal contains comments.
///
/// There is currently no fix available when applying the fix would lead to
/// a `TypeError` from an expression of the form `None | None` or when we
/// are unable to import the symbol `typing.Optional` and the Python version
/// is 3.9 or below.
///
/// ## References
/// - [Typing documentation: Legal parameters for `Literal` at type check time](https://typing.python.org/en/latest/spec/literal.html#legal-parameters-for-literal-at-type-check-time)
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.13.0")]
pub(crate) struct RedundantNoneLiteral {
    union_kind: UnionKind,
}

impl Violation for RedundantNoneLiteral {
    const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;

    #[derive_message_formats]
    fn message(&self) -> String {
        match self.union_kind {
            UnionKind::NoUnion => "Use `None` rather than `Literal[None]`".to_string(),
            UnionKind::TypingOptional => {
                "Use `Optional[Literal[...]]` rather than `Literal[None, ...]` ".to_string()
            }
            UnionKind::BitOr => {
                "Use `Literal[...] | None` rather than `Literal[None, ...]` ".to_string()
            }
        }
    }

    fn fix_title(&self) -> Option<String> {
        Some(match self.union_kind {
            UnionKind::NoUnion => "Replace with `None`".to_string(),
            UnionKind::TypingOptional => "Replace with `Optional[Literal[...]]`".to_string(),
            UnionKind::BitOr => "Replace with `Literal[...] | None`".to_string(),
        })
    }
}

/// PYI061
pub(crate) fn redundant_none_literal<'a>(checker: &Checker, literal_expr: &'a Expr) {
    let semantic = checker.semantic();

    if !semantic.seen_typing() {
        return;
    }

    let Expr::Subscript(ast::ExprSubscript {
        value: literal_subscript,
        ..
    }) = literal_expr
    else {
        return;
    };

    let mut none_exprs: SmallVec<[&ExprNoneLiteral; 1]> = SmallVec::new();
    let mut literal_elements = vec![];

    let mut partition_literal_elements = |expr: &'a Expr, _parent: &'a Expr| {
        if let Expr::NoneLiteral(none_expr) = expr {
            none_exprs.push(none_expr);
        } else {
            literal_elements.push(expr);
        }
    };

    traverse_literal(&mut partition_literal_elements, semantic, literal_expr);

    if none_exprs.is_empty() {
        return;
    }

    let union_kind = if literal_elements.is_empty() {
        UnionKind::NoUnion
    } else if (checker.target_version() >= PythonVersion::PY310) || checker.source_type.is_stub() {
        UnionKind::BitOr
    } else {
        UnionKind::TypingOptional
    };

    // N.B. Applying the fix can leave an unused import to be fixed by the `unused-import` rule.
    for none_expr in none_exprs {
        let mut diagnostic =
            checker.report_diagnostic(RedundantNoneLiteral { union_kind }, none_expr.range());
        diagnostic.try_set_optional_fix(|| {
            create_fix(
                checker,
                literal_expr,
                literal_subscript,
                literal_elements.clone(),
                union_kind,
            )
            // Isolate the fix to ensure multiple fixes on the same expression (like
            // `Literal[None,] | Literal[None,]` -> `None | None`) happen across separate passes,
            // preventing the production of invalid code.
            .map(|fix| {
                fix.map(|fix| fix.isolate(Checker::isolation(semantic.current_statement_id())))
            })
        });
    }
}

/// If possible, return a [`Fix`] for a violation of this rule.
///
/// Avoid producing code that would raise an exception when
/// `Literal[None] | None` would be fixed to `None | None`.
/// Instead, do not provide a fix. We don't need to worry about unions
/// that use [`typing.Union`], as `Union[None, None]` is valid Python.
/// See <https://github.com/astral-sh/ruff/issues/14567>.
///
/// [`typing.Union`]: https://docs.python.org/3/library/typing.html#typing.Union
fn create_fix(
    checker: &Checker,
    literal_expr: &Expr,
    literal_subscript: &Expr,
    literal_elements: Vec<&Expr>,
    union_kind: UnionKind,
) -> Result<Option<Fix>> {
    let semantic = checker.semantic();

    let enclosing_pep604_union = semantic
        .current_expressions()
        .skip(1)
        .take_while(|expr| {
            matches!(
                expr,
                Expr::BinOp(ExprBinOp {
                    op: Operator::BitOr,
                    ..
                })
            )
        })
        .last();

    if let Some(enclosing_pep604_union) = enclosing_pep604_union {
        let mut is_fixable = true;

        traverse_union(
            &mut |expr, _| {
                if expr.is_none_literal_expr() {
                    is_fixable = false;
                }
            },
            semantic,
            enclosing_pep604_union,
        );

        if !is_fixable {
            return Ok(None);
        }
    }

    let applicability = if checker.comment_ranges().intersects(literal_expr.range()) {
        Applicability::Unsafe
    } else {
        Applicability::Safe
    };

    if matches!(union_kind, UnionKind::NoUnion) {
        return Ok(Some(Fix::applicable_edit(
            Edit::range_replacement("None".to_string(), literal_expr.range()),
            applicability,
        )));
    }

    let new_literal_expr = Expr::Subscript(ast::ExprSubscript {
        value: Box::new(literal_subscript.clone()),
        range: TextRange::default(),
        node_index: ruff_python_ast::AtomicNodeIndex::NONE,
        ctx: ExprContext::Load,
        slice: Box::new(if literal_elements.len() > 1 {
            Expr::Tuple(ast::ExprTuple {
                elts: literal_elements.into_iter().cloned().collect(),
                range: TextRange::default(),
                node_index: ruff_python_ast::AtomicNodeIndex::NONE,
                ctx: ExprContext::Load,
                parenthesized: true,
            })
        } else {
            literal_elements[0].clone()
        }),
    });

    let fix = match union_kind {
        UnionKind::TypingOptional => {
            let Some(importer) = checker.typing_importer("Optional", PythonVersion::lowest())
            else {
                return Ok(None);
            };
            let (import_edit, bound_name) = importer.import(literal_expr.start())?;
            let optional_expr = typing_optional(new_literal_expr, Name::from(bound_name));
            let content = checker.generator().expr(&optional_expr);
            let optional_edit = Edit::range_replacement(content, literal_expr.range());
            Fix::applicable_edits(import_edit, [optional_edit], applicability)
        }
        UnionKind::BitOr => {
            let none_expr = Expr::NoneLiteral(ExprNoneLiteral {
                range: TextRange::default(),
                node_index: ruff_python_ast::AtomicNodeIndex::NONE,
            });
            let union_expr = pep_604_union(&[new_literal_expr, none_expr]);

            // Check if we need parentheses to preserve operator precedence
            let content =
                if needs_parentheses_for_precedence(semantic, literal_expr, checker.tokens()) {
                    format!("({})", checker.generator().expr(&union_expr))
                } else {
                    checker.generator().expr(&union_expr)
                };

            let union_edit = Edit::range_replacement(content, literal_expr.range());
            Fix::applicable_edit(union_edit, applicability)
        }
        // We dealt with this case earlier to avoid allocating `lhs` and `rhs`
        UnionKind::NoUnion => {
            unreachable!()
        }
    };
    Ok(Some(fix))
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum UnionKind {
    NoUnion,
    TypingOptional,
    BitOr,
}

/// Check if the union expression needs parentheses to preserve operator precedence.
/// This is needed when the union is part of a larger expression where the `|` operator
/// has lower precedence than the surrounding operations (like attribute access).
fn needs_parentheses_for_precedence(
    semantic: &ruff_python_semantic::SemanticModel,
    literal_expr: &Expr,
    tokens: &Tokens,
) -> bool {
    // Get the parent expression to check if we're in a context that needs parentheses
    let Some(parent_expr) = semantic.current_expression_parent() else {
        return false;
    };

    // Check if the literal expression is already parenthesized
    if parenthesized_range(literal_expr.into(), parent_expr.into(), tokens).is_some() {
        return false; // Already parenthesized, don't add more
    }

    // Check if the parent expression has higher precedence than the `|` operator
    let union_precedence = OperatorPrecedence::BitOr;
    let parent_precedence = OperatorPrecedence::from(parent_expr);

    // If the parent operation has higher precedence than `|`, we need parentheses
    parent_precedence > union_precedence
}
