use serde::de::Error as DeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SourceLocation {
    pub line: usize,
    pub column: usize,
    pub offset: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SourceSpan {
    pub path: String,
    pub start: SourceLocation,
    pub end: Option<SourceLocation>,
}

#[derive(
    Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Default,
)]
#[serde(rename_all = "snake_case")]
pub enum Severity {
    Info,
    #[default]
    Warning,
    Error,
}

impl fmt::Display for Severity {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let value = match self {
            Self::Info => "info",
            Self::Warning => "warning",
            Self::Error => "error",
        };
        f.write_str(value)
    }
}

impl FromStr for Severity {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "info" => Ok(Self::Info),
            "warning" => Ok(Self::Warning),
            "error" => Ok(Self::Error),
            _ => Err(format!("unsupported severity: {value}")),
        }
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum FixKind {
    ManualReview,
    ExtractEffectBoundary,
    InjectDependency,
    ReturnNewValue,
    ReplaceMutableDefault,
    AvoidInPlaceMutation,
    EliminateLoopEffect,
    ReplaceExceptionFlow,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FixHint {
    pub kind: FixKind,
    pub summary: String,
    pub suggested_action: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum RuleId {
    ParseError,
    MutableGlobalState,
    AssignmentOveruse,
    DirectIo,
    NonDeterministicUse,
    ExternalStateSideEffect,
    MutableDefaultArgument,
    DirectEffectfulCall,
    GlobalNonlocalUsage,
    HiddenMutation,
    LoopSideEffect,
    ExceptionControlFlow,
    ClassMethodSideEffect,
    ReferentialTransparencyBreak,
    DeprecatedApiUse,
    Custom(String),
}

impl RuleId {
    #[must_use]
    pub const fn all() -> [Self; 15] {
        [
            Self::ParseError,
            Self::MutableGlobalState,
            Self::AssignmentOveruse,
            Self::DirectIo,
            Self::NonDeterministicUse,
            Self::ExternalStateSideEffect,
            Self::MutableDefaultArgument,
            Self::DirectEffectfulCall,
            Self::GlobalNonlocalUsage,
            Self::HiddenMutation,
            Self::LoopSideEffect,
            Self::ExceptionControlFlow,
            Self::ClassMethodSideEffect,
            Self::ReferentialTransparencyBreak,
            Self::DeprecatedApiUse,
        ]
    }

    #[must_use]
    pub fn as_str(&self) -> &str {
        match self {
            Self::ParseError => "parse_error",
            Self::MutableGlobalState => "mutable_global_state",
            Self::AssignmentOveruse => "assignment_overuse",
            Self::DirectIo => "direct_io",
            Self::NonDeterministicUse => "non_deterministic_use",
            Self::ExternalStateSideEffect => "external_state_side_effect",
            Self::MutableDefaultArgument => "mutable_default_argument",
            Self::DirectEffectfulCall => "direct_effectful_call",
            Self::GlobalNonlocalUsage => "global_nonlocal_usage",
            Self::HiddenMutation => "hidden_mutation",
            Self::LoopSideEffect => "loop_side_effect",
            Self::ExceptionControlFlow => "exception_control_flow",
            Self::ClassMethodSideEffect => "class_method_side_effect",
            Self::ReferentialTransparencyBreak => "referential_transparency_break",
            Self::DeprecatedApiUse => "deprecated_api_use",
            Self::Custom(id) => id.as_str(),
        }
    }

    #[must_use]
    pub fn is_builtin(&self) -> bool {
        !matches!(self, Self::Custom(_))
    }
}

impl fmt::Display for RuleId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl FromStr for RuleId {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        if let Some(rule_id) = RuleId::all()
            .into_iter()
            .find(|rule_id| rule_id.as_str() == value)
        {
            return Ok(rule_id);
        }

        if is_valid_rule_id(value) {
            Ok(Self::Custom(value.to_owned()))
        } else {
            Err(format!("unsupported rule id: {value}"))
        }
    }
}

impl Serialize for RuleId {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(self.as_str())
    }
}

impl<'de> Deserialize<'de> for RuleId {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = String::deserialize(deserializer)?;
        Self::from_str(&value).map_err(D::Error::custom)
    }
}

fn is_valid_rule_id(value: &str) -> bool {
    let mut chars = value.chars();
    matches!(chars.next(), Some(first) if first.is_ascii_lowercase())
        && chars.all(|char| char.is_ascii_lowercase() || char.is_ascii_digit() || char == '_')
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Diagnostic {
    pub rule_id: RuleId,
    pub severity: Severity,
    pub message: String,
    pub path: String,
    pub function: Option<String>,
    pub location: SourceSpan,
    pub hint: Option<String>,
    pub fix_hint: Option<FixHint>,
}

impl Diagnostic {
    pub fn new(
        rule_id: RuleId,
        severity: Severity,
        message: impl Into<String>,
        function: Option<String>,
        location: SourceSpan,
        hint: Option<String>,
        fix_hint: Option<FixHint>,
    ) -> Self {
        Self {
            rule_id,
            severity,
            message: message.into(),
            path: location.path.clone(),
            function,
            location,
            hint,
            fix_hint,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct IgnoreDirective {
    pub decorator_name: String,
    pub location: SourceSpan,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Module {
    pub path: String,
    pub body: Vec<Statement>,
    pub functions: Vec<FunctionDef>,
    pub globals: Vec<GlobalBinding>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GlobalBinding {
    pub name: String,
    pub is_mutable: bool,
    pub location: SourceSpan,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FunctionDef {
    pub name: String,
    pub qualified_name: String,
    pub class_name: Option<String>,
    pub is_async: bool,
    pub parameters: Vec<Parameter>,
    pub returns: Option<Expression>,
    pub decorators: Vec<Expression>,
    pub body: Vec<Statement>,
    pub location: SourceSpan,
    pub ignore: Option<IgnoreDirective>,
    pub local_bindings: Vec<String>,
}

impl FunctionDef {
    #[must_use]
    pub fn is_ignored(&self) -> bool {
        self.ignore.is_some()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Parameter {
    pub name: String,
    pub kind: ParameterKind,
    pub annotation: Option<Expression>,
    pub default: Option<Expression>,
    pub location: SourceSpan,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ParameterKind {
    PositionalOnly,
    Positional,
    VarArg,
    KeywordOnly,
    KwArg,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Statement {
    pub kind: StatementKind,
    pub location: SourceSpan,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AssignKind {
    Simple,
    Annotated,
    Augmented,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ControlFlowKind {
    Pass,
    Break,
    Continue,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WithItem {
    pub context_expr: Expression,
    pub optional_vars: Option<Expression>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ExceptHandler {
    pub type_expr: Option<Expression>,
    pub name: Option<String>,
    pub body: Vec<Statement>,
    pub location: SourceSpan,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum StatementKind {
    FunctionDef(Box<FunctionDef>),
    ClassDef {
        name: String,
        bases: Vec<Expression>,
        decorators: Vec<Expression>,
        body: Vec<Statement>,
    },
    Return {
        value: Option<Expression>,
    },
    Delete {
        targets: Vec<Expression>,
    },
    Assign {
        targets: Vec<Expression>,
        value: Option<Expression>,
        annotation: Option<Expression>,
        kind: AssignKind,
    },
    For {
        target: Expression,
        iter: Expression,
        body: Vec<Statement>,
        orelse: Vec<Statement>,
        is_async: bool,
    },
    While {
        test: Expression,
        body: Vec<Statement>,
        orelse: Vec<Statement>,
    },
    If {
        test: Expression,
        body: Vec<Statement>,
        orelse: Vec<Statement>,
    },
    With {
        items: Vec<WithItem>,
        body: Vec<Statement>,
        is_async: bool,
    },
    Try {
        body: Vec<Statement>,
        handlers: Vec<ExceptHandler>,
        orelse: Vec<Statement>,
        finalbody: Vec<Statement>,
    },
    Raise {
        exc: Option<Expression>,
        cause: Option<Expression>,
    },
    Assert {
        test: Expression,
        message: Option<Expression>,
    },
    Import {
        module: Option<String>,
        names: Vec<String>,
    },
    Global {
        names: Vec<String>,
    },
    Nonlocal {
        names: Vec<String>,
    },
    Expression {
        expression: Expression,
    },
    ControlFlow {
        kind: ControlFlowKind,
    },
    Other {
        label: String,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Expression {
    pub kind: ExpressionKind,
    pub location: SourceSpan,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct KeywordArg {
    pub name: Option<String>,
    pub value: Expression,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DictEntry {
    pub key: Option<Expression>,
    pub value: Expression,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ConstantKind {
    None,
    Bool,
    String,
    Bytes,
    Integer,
    Float,
    Complex,
    Tuple,
    Ellipsis,
    Unknown,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ExpressionKind {
    Name {
        id: String,
    },
    Attribute {
        value: Box<Expression>,
        attr: String,
    },
    Call {
        function: Box<Expression>,
        args: Vec<Expression>,
        keywords: Vec<KeywordArg>,
    },
    Constant {
        kind: ConstantKind,
    },
    List {
        elements: Vec<Expression>,
    },
    Tuple {
        elements: Vec<Expression>,
    },
    Set {
        elements: Vec<Expression>,
    },
    Dict {
        entries: Vec<DictEntry>,
    },
    Binary {
        left: Box<Expression>,
        operator: String,
        right: Box<Expression>,
    },
    Unary {
        operator: String,
        operand: Box<Expression>,
    },
    BoolOp {
        operator: String,
        values: Vec<Expression>,
    },
    Compare {
        left: Box<Expression>,
        operators: Vec<String>,
        comparators: Vec<Expression>,
    },
    IfElse {
        test: Box<Expression>,
        body: Box<Expression>,
        orelse: Box<Expression>,
    },
    Lambda,
    Subscript {
        value: Box<Expression>,
        slice: Box<Expression>,
    },
    Await {
        value: Box<Expression>,
    },
    Yield {
        value: Option<Box<Expression>>,
    },
    YieldFrom {
        value: Box<Expression>,
    },
    Named {
        target: Box<Expression>,
        value: Box<Expression>,
    },
    Starred {
        value: Box<Expression>,
    },
    Slice {
        lower: Option<Box<Expression>>,
        upper: Option<Box<Expression>>,
        step: Option<Box<Expression>>,
    },
    Other {
        label: String,
    },
}

impl Expression {
    pub fn path(&self) -> Option<String> {
        match &self.kind {
            ExpressionKind::Name { id } => Some(id.clone()),
            ExpressionKind::Attribute { value, attr } => value
                .path()
                .map(|value_path| format!("{value_path}.{attr}")),
            _ => None,
        }
    }

    pub fn last_segment(&self) -> Option<String> {
        self.path()
            .and_then(|path| path.rsplit('.').next().map(ToOwned::to_owned))
    }

    pub fn base_name(&self) -> Option<String> {
        match &self.kind {
            ExpressionKind::Name { id } => Some(id.clone()),
            ExpressionKind::Attribute { value, .. } | ExpressionKind::Subscript { value, .. } => {
                value.base_name()
            }
            _ => None,
        }
    }

    pub fn is_mutable_value(&self) -> bool {
        match &self.kind {
            ExpressionKind::List { .. }
            | ExpressionKind::Set { .. }
            | ExpressionKind::Dict { .. } => true,
            ExpressionKind::Call { function, .. } => function.path().is_some_and(|path| {
                matches!(path.as_str(), "list" | "dict" | "set" | "collections.deque")
            }),
            _ => false,
        }
    }
}
