use std::str::FromStr;

use itertools::Itertools;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumIter, EnumString};

use crate::ast::checkers::Primitive;
use crate::ast::types::Range;

pub const DEFAULT_CHECK_CODES: [CheckCode; 43] = [
    // pycodestyle errors
    CheckCode::E402,
    CheckCode::E501,
    CheckCode::E711,
    CheckCode::E712,
    CheckCode::E713,
    CheckCode::E714,
    CheckCode::E721,
    CheckCode::E722,
    CheckCode::E731,
    CheckCode::E741,
    CheckCode::E742,
    CheckCode::E743,
    CheckCode::E902,
    CheckCode::E999,
    // pycodestyle warnings
    CheckCode::W292,
    // pyflakes
    CheckCode::F401,
    CheckCode::F402,
    CheckCode::F403,
    CheckCode::F404,
    CheckCode::F405,
    CheckCode::F406,
    CheckCode::F407,
    CheckCode::F541,
    CheckCode::F601,
    CheckCode::F602,
    CheckCode::F621,
    CheckCode::F622,
    CheckCode::F631,
    CheckCode::F632,
    CheckCode::F633,
    CheckCode::F634,
    CheckCode::F701,
    CheckCode::F702,
    CheckCode::F704,
    CheckCode::F706,
    CheckCode::F707,
    CheckCode::F722,
    CheckCode::F821,
    CheckCode::F822,
    CheckCode::F823,
    CheckCode::F831,
    CheckCode::F841,
    CheckCode::F901,
];

#[derive(
    AsRefStr,
    EnumIter,
    EnumString,
    Debug,
    PartialEq,
    Eq,
    Clone,
    Serialize,
    Deserialize,
    Hash,
    PartialOrd,
    Ord,
)]
pub enum CheckCode {
    // pycodestyle errors
    E402,
    E501,
    E711,
    E712,
    E713,
    E714,
    E721,
    E722,
    E731,
    E741,
    E742,
    E743,
    E902,
    E999,
    // pycodestyle warnings
    W292,
    // pyflakes
    F401,
    F402,
    F403,
    F404,
    F405,
    F406,
    F407,
    F541,
    F601,
    F602,
    F621,
    F622,
    F631,
    F632,
    F633,
    F634,
    F701,
    F702,
    F704,
    F706,
    F707,
    F722,
    F821,
    F822,
    F823,
    F831,
    F841,
    F901,
    // flake8-builtins
    A001,
    A002,
    A003,
    // flake8-bugbear
    B011,
    B014,
    B025,
    // flake8-comprehensions
    C400,
    C401,
    C402,
    C403,
    C404,
    C405,
    C406,
    C408,
    C409,
    C410,
    C411,
    C413,
    C414,
    C415,
    C416,
    C417,
    // flake8-print
    T201,
    T203,
    // pyupgrade
    U001,
    U002,
    U003,
    U004,
    U005,
    U006,
    U007,
    U008,
    // pydocstyle
    D100,
    D101,
    D102,
    D103,
    D104,
    D105,
    D106,
    D107,
    D200,
    D201,
    D202,
    D203,
    D204,
    D205,
    D206,
    D207,
    D208,
    D209,
    D210,
    D211,
    D212,
    D213,
    D214,
    D215,
    D300,
    D400,
    D402,
    D403,
    D404,
    D405,
    D406,
    D407,
    D408,
    D409,
    D410,
    D411,
    D412,
    D413,
    D414,
    D415,
    D416,
    D417,
    D418,
    D419,
    // Meta
    M001,
}

#[derive(EnumIter, Debug, PartialEq, Eq)]
pub enum CheckCategory {
    Pyflakes,
    Pycodestyle,
    Pydocstyle,
    Pyupgrade,
    Flake8Comprehensions,
    Flake8Bugbear,
    Flake8Builtins,
    Flake8Print,
    Meta,
}

impl CheckCategory {
    pub fn title(&self) -> &'static str {
        match self {
            CheckCategory::Pycodestyle => "pycodestyle",
            CheckCategory::Pyflakes => "Pyflakes",
            CheckCategory::Flake8Builtins => "flake8-builtins",
            CheckCategory::Flake8Bugbear => "flake8-bugbear",
            CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
            CheckCategory::Flake8Print => "flake8-print",
            CheckCategory::Pyupgrade => "pyupgrade",
            CheckCategory::Pydocstyle => "pydocstyle",
            CheckCategory::Meta => "Meta rules",
        }
    }
}

#[allow(clippy::upper_case_acronyms)]
pub enum LintSource {
    AST,
    Lines,
    FileSystem,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum RejectedCmpop {
    Eq,
    NotEq,
}

#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
    // pycodestyle errors
    AmbiguousClassName(String),
    AmbiguousFunctionName(String),
    AmbiguousVariableName(String),
    AssertTuple,
    BreakOutsideLoop,
    ContinueOutsideLoop,
    DefaultExceptNotLast,
    DoNotAssignLambda,
    DoNotUseBareExcept,
    DuplicateArgumentName,
    ExpressionsInStarAssignment,
    FStringMissingPlaceholders,
    ForwardAnnotationSyntaxError(String),
    FutureFeatureNotDefined(String),
    IOError(String),
    IfTuple,
    ImportShadowedByLoopVar(String, usize),
    ImportStarNotPermitted(String),
    ImportStarUsage(String, Vec<String>),
    ImportStarUsed(String),
    InvalidPrintSyntax,
    IsLiteral,
    LateFutureImport,
    LineTooLong(usize, usize),
    ModuleImportNotAtTopOfFile,
    MultiValueRepeatedKeyLiteral,
    MultiValueRepeatedKeyVariable(String),
    NoneComparison(RejectedCmpop),
    NotInTest,
    NotIsTest,
    RaiseNotImplemented,
    ReturnOutsideFunction,
    SyntaxError(String),
    TrueFalseComparison(bool, RejectedCmpop),
    TwoStarredExpressions,
    TypeComparison,
    UndefinedExport(String),
    UndefinedLocal(String),
    UndefinedName(String),
    UnusedImport(Vec<String>),
    UnusedVariable(String),
    YieldOutsideFunction,
    // pycodestyle warnings
    NoNewLineAtEndOfFile,
    // flake8-builtin
    BuiltinVariableShadowing(String),
    BuiltinArgumentShadowing(String),
    BuiltinAttributeShadowing(String),
    // flake8-bugbear
    DoNotAssertFalse,
    DuplicateHandlerException(Vec<String>),
    DuplicateTryBlockException(String),
    // flake8-comprehensions
    UnnecessaryGeneratorList,
    UnnecessaryGeneratorSet,
    UnnecessaryGeneratorDict,
    UnnecessaryListComprehensionSet,
    UnnecessaryListComprehensionDict,
    UnnecessaryLiteralSet(String),
    UnnecessaryLiteralDict(String),
    UnnecessaryCollectionCall(String),
    UnnecessaryLiteralWithinTupleCall(String),
    UnnecessaryLiteralWithinListCall(String),
    UnnecessaryListCall,
    UnnecessaryCallAroundSorted(String),
    UnnecessaryDoubleCastOrProcess(String, String),
    UnnecessarySubscriptReversal(String),
    UnnecessaryComprehension(String),
    UnnecessaryMap(String),
    // flake8-print
    PrintFound,
    PPrintFound,
    // pyupgrade
    TypeOfPrimitive(Primitive),
    UnnecessaryAbspath,
    UselessMetaclassType,
    DeprecatedUnittestAlias(String, String),
    UselessObjectInheritance(String),
    UsePEP585Annotation(String),
    UsePEP604Annotation,
    SuperCallWithParameters,
    // pydocstyle
    BlankLineAfterLastSection(String),
    BlankLineAfterSection(String),
    BlankLineBeforeSection(String),
    CapitalizeSectionName(String),
    DashedUnderlineAfterSection(String),
    DocumentAllArguments(Vec<String>),
    EndsInPeriod,
    EndsInPunctuation,
    FirstLineCapitalized,
    FitsOnOneLine,
    IndentWithSpaces,
    MagicMethod,
    MultiLineSummaryFirstLine,
    MultiLineSummarySecondLine,
    NewLineAfterLastParagraph,
    NewLineAfterSectionName(String),
    NoBlankLineAfterFunction(usize),
    NoBlankLineAfterSummary,
    NoBlankLineBeforeClass(usize),
    NoBlankLineBeforeFunction(usize),
    NoBlankLinesBetweenHeaderAndContent(String),
    NoOverIndentation,
    NoSignature,
    NoSurroundingWhitespace,
    NoThisPrefix,
    NoUnderIndentation,
    NonEmpty,
    NonEmptySection(String),
    OneBlankLineAfterClass(usize),
    OneBlankLineBeforeClass(usize),
    PublicClass,
    PublicFunction,
    PublicInit,
    PublicMethod,
    PublicModule,
    PublicNestedClass,
    PublicPackage,
    SectionNameEndsInColon(String),
    SectionNotOverIndented(String),
    SectionUnderlineAfterName(String),
    SectionUnderlineMatchesSectionLength(String),
    SectionUnderlineNotOverIndented(String),
    SkipDocstring,
    UsesTripleQuotes,
    // Meta
    UnusedNOQA(Option<Vec<String>>),
}

impl CheckCode {
    /// The source for the check (either the AST, the filesystem, or the physical lines).
    pub fn lint_source(&self) -> &'static LintSource {
        match self {
            CheckCode::E501 | CheckCode::W292 | CheckCode::M001 => &LintSource::Lines,
            CheckCode::E902 => &LintSource::FileSystem,
            _ => &LintSource::AST,
        }
    }

    /// A placeholder representation of the CheckKind for the check.
    pub fn kind(&self) -> CheckKind {
        match self {
            // pycodestyle errors
            CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
            CheckCode::E501 => CheckKind::LineTooLong(89, 88),
            CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
            CheckCode::E712 => CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
            CheckCode::E713 => CheckKind::NotInTest,
            CheckCode::E714 => CheckKind::NotIsTest,
            CheckCode::E721 => CheckKind::TypeComparison,
            CheckCode::E722 => CheckKind::DoNotUseBareExcept,
            CheckCode::E731 => CheckKind::DoNotAssignLambda,
            CheckCode::E741 => CheckKind::AmbiguousVariableName("...".to_string()),
            CheckCode::E742 => CheckKind::AmbiguousClassName("...".to_string()),
            CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()),
            CheckCode::E902 => CheckKind::IOError("IOError: `...`".to_string()),
            CheckCode::E999 => CheckKind::SyntaxError("`...`".to_string()),
            // pycodestyle warnings
            CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
            // pyflakes
            CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()]),
            CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
            CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
            CheckCode::F404 => CheckKind::LateFutureImport,
            CheckCode::F405 => {
                CheckKind::ImportStarUsage("...".to_string(), vec!["...".to_string()])
            }
            CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
            CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
            CheckCode::F541 => CheckKind::FStringMissingPlaceholders,
            CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral,
            CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
            CheckCode::F621 => CheckKind::ExpressionsInStarAssignment,
            CheckCode::F622 => CheckKind::TwoStarredExpressions,
            CheckCode::F631 => CheckKind::AssertTuple,
            CheckCode::F632 => CheckKind::IsLiteral,
            CheckCode::F633 => CheckKind::InvalidPrintSyntax,
            CheckCode::F634 => CheckKind::IfTuple,
            CheckCode::F701 => CheckKind::BreakOutsideLoop,
            CheckCode::F702 => CheckKind::ContinueOutsideLoop,
            CheckCode::F704 => CheckKind::YieldOutsideFunction,
            CheckCode::F706 => CheckKind::ReturnOutsideFunction,
            CheckCode::F707 => CheckKind::DefaultExceptNotLast,
            CheckCode::F722 => CheckKind::ForwardAnnotationSyntaxError("...".to_string()),
            CheckCode::F821 => CheckKind::UndefinedName("...".to_string()),
            CheckCode::F822 => CheckKind::UndefinedExport("...".to_string()),
            CheckCode::F823 => CheckKind::UndefinedLocal("...".to_string()),
            CheckCode::F831 => CheckKind::DuplicateArgumentName,
            CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
            CheckCode::F901 => CheckKind::RaiseNotImplemented,
            // flake8-builtins
            CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
            CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
            CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
            // flake8-bugbear
            CheckCode::B011 => CheckKind::DoNotAssertFalse,
            CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
            CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
            // flake8-comprehensions
            CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
            CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
            CheckCode::C402 => CheckKind::UnnecessaryGeneratorDict,
            CheckCode::C403 => CheckKind::UnnecessaryListComprehensionSet,
            CheckCode::C404 => CheckKind::UnnecessaryListComprehensionDict,
            CheckCode::C405 => CheckKind::UnnecessaryLiteralSet("<list/tuple>".to_string()),
            CheckCode::C406 => CheckKind::UnnecessaryLiteralDict("<list/tuple>".to_string()),
            CheckCode::C408 => {
                CheckKind::UnnecessaryCollectionCall("<dict/list/tuple>".to_string())
            }
            CheckCode::C409 => {
                CheckKind::UnnecessaryLiteralWithinTupleCall("<list/tuple>".to_string())
            }
            CheckCode::C410 => {
                CheckKind::UnnecessaryLiteralWithinListCall("<list/tuple>".to_string())
            }
            CheckCode::C411 => CheckKind::UnnecessaryListCall,
            CheckCode::C413 => {
                CheckKind::UnnecessaryCallAroundSorted("<list/reversed>".to_string())
            }
            CheckCode::C414 => CheckKind::UnnecessaryDoubleCastOrProcess(
                "<list/reversed/set/sorted/tuple>".to_string(),
                "<list/set/sorted/tuple>".to_string(),
            ),
            CheckCode::C415 => {
                CheckKind::UnnecessarySubscriptReversal("<reversed/set/sorted>".to_string())
            }
            CheckCode::C416 => CheckKind::UnnecessaryComprehension("<list/set>".to_string()),
            CheckCode::C417 => CheckKind::UnnecessaryMap("<list/set/dict>".to_string()),
            // flake8-print
            CheckCode::T201 => CheckKind::PrintFound,
            CheckCode::T203 => CheckKind::PPrintFound,
            // pyupgrade
            CheckCode::U001 => CheckKind::UselessMetaclassType,
            CheckCode::U002 => CheckKind::UnnecessaryAbspath,
            CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
            CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
            CheckCode::U005 => CheckKind::DeprecatedUnittestAlias(
                "assertEquals".to_string(),
                "assertEqual".to_string(),
            ),
            CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
            CheckCode::U007 => CheckKind::UsePEP604Annotation,
            CheckCode::U008 => CheckKind::SuperCallWithParameters,
            // pydocstyle
            CheckCode::D100 => CheckKind::PublicModule,
            CheckCode::D101 => CheckKind::PublicClass,
            CheckCode::D102 => CheckKind::PublicMethod,
            CheckCode::D103 => CheckKind::PublicFunction,
            CheckCode::D104 => CheckKind::PublicPackage,
            CheckCode::D105 => CheckKind::MagicMethod,
            CheckCode::D106 => CheckKind::PublicNestedClass,
            CheckCode::D107 => CheckKind::PublicInit,
            CheckCode::D200 => CheckKind::FitsOnOneLine,
            CheckCode::D201 => CheckKind::NoBlankLineBeforeFunction(1),
            CheckCode::D202 => CheckKind::NoBlankLineAfterFunction(1),
            CheckCode::D203 => CheckKind::OneBlankLineBeforeClass(0),
            CheckCode::D204 => CheckKind::OneBlankLineAfterClass(0),
            CheckCode::D205 => CheckKind::NoBlankLineAfterSummary,
            CheckCode::D206 => CheckKind::IndentWithSpaces,
            CheckCode::D207 => CheckKind::NoUnderIndentation,
            CheckCode::D208 => CheckKind::NoOverIndentation,
            CheckCode::D209 => CheckKind::NewLineAfterLastParagraph,
            CheckCode::D210 => CheckKind::NoSurroundingWhitespace,
            CheckCode::D211 => CheckKind::NoBlankLineBeforeClass(1),
            CheckCode::D212 => CheckKind::MultiLineSummaryFirstLine,
            CheckCode::D213 => CheckKind::MultiLineSummarySecondLine,
            CheckCode::D214 => CheckKind::SectionNotOverIndented("Returns".to_string()),
            CheckCode::D215 => CheckKind::SectionUnderlineNotOverIndented("Returns".to_string()),
            CheckCode::D300 => CheckKind::UsesTripleQuotes,
            CheckCode::D400 => CheckKind::EndsInPeriod,
            CheckCode::D402 => CheckKind::NoSignature,
            CheckCode::D403 => CheckKind::FirstLineCapitalized,
            CheckCode::D404 => CheckKind::NoThisPrefix,
            CheckCode::D405 => CheckKind::CapitalizeSectionName("returns".to_string()),
            CheckCode::D406 => CheckKind::NewLineAfterSectionName("Returns".to_string()),
            CheckCode::D407 => CheckKind::DashedUnderlineAfterSection("Returns".to_string()),
            CheckCode::D408 => CheckKind::SectionUnderlineAfterName("Returns".to_string()),
            CheckCode::D409 => {
                CheckKind::SectionUnderlineMatchesSectionLength("Returns".to_string())
            }
            CheckCode::D410 => CheckKind::BlankLineAfterSection("Returns".to_string()),
            CheckCode::D411 => CheckKind::BlankLineBeforeSection("Returns".to_string()),
            CheckCode::D412 => {
                CheckKind::NoBlankLinesBetweenHeaderAndContent("Returns".to_string())
            }
            CheckCode::D413 => CheckKind::BlankLineAfterLastSection("Returns".to_string()),
            CheckCode::D414 => CheckKind::NonEmptySection("Returns".to_string()),
            CheckCode::D415 => CheckKind::EndsInPunctuation,
            CheckCode::D416 => CheckKind::SectionNameEndsInColon("Returns".to_string()),
            CheckCode::D417 => {
                CheckKind::DocumentAllArguments(vec!["x".to_string(), "y".to_string()])
            }
            CheckCode::D418 => CheckKind::SkipDocstring,
            CheckCode::D419 => CheckKind::NonEmpty,
            // Meta
            CheckCode::M001 => CheckKind::UnusedNOQA(None),
        }
    }

    pub fn category(&self) -> CheckCategory {
        match self {
            CheckCode::E402 => CheckCategory::Pycodestyle,
            CheckCode::E501 => CheckCategory::Pycodestyle,
            CheckCode::E711 => CheckCategory::Pycodestyle,
            CheckCode::E712 => CheckCategory::Pycodestyle,
            CheckCode::E713 => CheckCategory::Pycodestyle,
            CheckCode::E714 => CheckCategory::Pycodestyle,
            CheckCode::E721 => CheckCategory::Pycodestyle,
            CheckCode::E722 => CheckCategory::Pycodestyle,
            CheckCode::E731 => CheckCategory::Pycodestyle,
            CheckCode::E741 => CheckCategory::Pycodestyle,
            CheckCode::E742 => CheckCategory::Pycodestyle,
            CheckCode::E743 => CheckCategory::Pycodestyle,
            CheckCode::E902 => CheckCategory::Pycodestyle,
            CheckCode::E999 => CheckCategory::Pycodestyle,
            CheckCode::W292 => CheckCategory::Pycodestyle,
            CheckCode::F401 => CheckCategory::Pyflakes,
            CheckCode::F402 => CheckCategory::Pyflakes,
            CheckCode::F403 => CheckCategory::Pyflakes,
            CheckCode::F404 => CheckCategory::Pyflakes,
            CheckCode::F405 => CheckCategory::Pyflakes,
            CheckCode::F406 => CheckCategory::Pyflakes,
            CheckCode::F407 => CheckCategory::Pyflakes,
            CheckCode::F541 => CheckCategory::Pyflakes,
            CheckCode::F601 => CheckCategory::Pyflakes,
            CheckCode::F602 => CheckCategory::Pyflakes,
            CheckCode::F621 => CheckCategory::Pyflakes,
            CheckCode::F622 => CheckCategory::Pyflakes,
            CheckCode::F631 => CheckCategory::Pyflakes,
            CheckCode::F632 => CheckCategory::Pyflakes,
            CheckCode::F633 => CheckCategory::Pyflakes,
            CheckCode::F634 => CheckCategory::Pyflakes,
            CheckCode::F701 => CheckCategory::Pyflakes,
            CheckCode::F702 => CheckCategory::Pyflakes,
            CheckCode::F704 => CheckCategory::Pyflakes,
            CheckCode::F706 => CheckCategory::Pyflakes,
            CheckCode::F707 => CheckCategory::Pyflakes,
            CheckCode::F722 => CheckCategory::Pyflakes,
            CheckCode::F821 => CheckCategory::Pyflakes,
            CheckCode::F822 => CheckCategory::Pyflakes,
            CheckCode::F823 => CheckCategory::Pyflakes,
            CheckCode::F831 => CheckCategory::Pyflakes,
            CheckCode::F841 => CheckCategory::Pyflakes,
            CheckCode::F901 => CheckCategory::Pyflakes,
            CheckCode::A001 => CheckCategory::Flake8Builtins,
            CheckCode::A002 => CheckCategory::Flake8Builtins,
            CheckCode::A003 => CheckCategory::Flake8Builtins,
            CheckCode::B011 => CheckCategory::Flake8Bugbear,
            CheckCode::B014 => CheckCategory::Flake8Bugbear,
            CheckCode::B025 => CheckCategory::Flake8Bugbear,
            CheckCode::C400 => CheckCategory::Flake8Comprehensions,
            CheckCode::C401 => CheckCategory::Flake8Comprehensions,
            CheckCode::C402 => CheckCategory::Flake8Comprehensions,
            CheckCode::C403 => CheckCategory::Flake8Comprehensions,
            CheckCode::C404 => CheckCategory::Flake8Comprehensions,
            CheckCode::C405 => CheckCategory::Flake8Comprehensions,
            CheckCode::C406 => CheckCategory::Flake8Comprehensions,
            CheckCode::C408 => CheckCategory::Flake8Comprehensions,
            CheckCode::C409 => CheckCategory::Flake8Comprehensions,
            CheckCode::C410 => CheckCategory::Flake8Comprehensions,
            CheckCode::C411 => CheckCategory::Flake8Comprehensions,
            CheckCode::C413 => CheckCategory::Flake8Comprehensions,
            CheckCode::C414 => CheckCategory::Flake8Comprehensions,
            CheckCode::C415 => CheckCategory::Flake8Comprehensions,
            CheckCode::C416 => CheckCategory::Flake8Comprehensions,
            CheckCode::C417 => CheckCategory::Flake8Comprehensions,
            CheckCode::T201 => CheckCategory::Flake8Print,
            CheckCode::T203 => CheckCategory::Flake8Print,
            CheckCode::U001 => CheckCategory::Pyupgrade,
            CheckCode::U002 => CheckCategory::Pyupgrade,
            CheckCode::U003 => CheckCategory::Pyupgrade,
            CheckCode::U004 => CheckCategory::Pyupgrade,
            CheckCode::U005 => CheckCategory::Pyupgrade,
            CheckCode::U006 => CheckCategory::Pyupgrade,
            CheckCode::U007 => CheckCategory::Pyupgrade,
            CheckCode::U008 => CheckCategory::Pyupgrade,
            CheckCode::D100 => CheckCategory::Pydocstyle,
            CheckCode::D101 => CheckCategory::Pydocstyle,
            CheckCode::D102 => CheckCategory::Pydocstyle,
            CheckCode::D103 => CheckCategory::Pydocstyle,
            CheckCode::D104 => CheckCategory::Pydocstyle,
            CheckCode::D105 => CheckCategory::Pydocstyle,
            CheckCode::D106 => CheckCategory::Pydocstyle,
            CheckCode::D107 => CheckCategory::Pydocstyle,
            CheckCode::D200 => CheckCategory::Pydocstyle,
            CheckCode::D201 => CheckCategory::Pydocstyle,
            CheckCode::D202 => CheckCategory::Pydocstyle,
            CheckCode::D203 => CheckCategory::Pydocstyle,
            CheckCode::D204 => CheckCategory::Pydocstyle,
            CheckCode::D205 => CheckCategory::Pydocstyle,
            CheckCode::D206 => CheckCategory::Pydocstyle,
            CheckCode::D207 => CheckCategory::Pydocstyle,
            CheckCode::D208 => CheckCategory::Pydocstyle,
            CheckCode::D209 => CheckCategory::Pydocstyle,
            CheckCode::D210 => CheckCategory::Pydocstyle,
            CheckCode::D211 => CheckCategory::Pydocstyle,
            CheckCode::D212 => CheckCategory::Pydocstyle,
            CheckCode::D213 => CheckCategory::Pydocstyle,
            CheckCode::D214 => CheckCategory::Pydocstyle,
            CheckCode::D215 => CheckCategory::Pydocstyle,
            CheckCode::D300 => CheckCategory::Pydocstyle,
            CheckCode::D400 => CheckCategory::Pydocstyle,
            CheckCode::D402 => CheckCategory::Pydocstyle,
            CheckCode::D403 => CheckCategory::Pydocstyle,
            CheckCode::D404 => CheckCategory::Pydocstyle,
            CheckCode::D405 => CheckCategory::Pydocstyle,
            CheckCode::D406 => CheckCategory::Pydocstyle,
            CheckCode::D407 => CheckCategory::Pydocstyle,
            CheckCode::D408 => CheckCategory::Pydocstyle,
            CheckCode::D409 => CheckCategory::Pydocstyle,
            CheckCode::D410 => CheckCategory::Pydocstyle,
            CheckCode::D411 => CheckCategory::Pydocstyle,
            CheckCode::D412 => CheckCategory::Pydocstyle,
            CheckCode::D413 => CheckCategory::Pydocstyle,
            CheckCode::D414 => CheckCategory::Pydocstyle,
            CheckCode::D415 => CheckCategory::Pydocstyle,
            CheckCode::D416 => CheckCategory::Pydocstyle,
            CheckCode::D417 => CheckCategory::Pydocstyle,
            CheckCode::D418 => CheckCategory::Pydocstyle,
            CheckCode::D419 => CheckCategory::Pydocstyle,
            CheckCode::M001 => CheckCategory::Meta,
        }
    }
}

impl CheckKind {
    /// A four-letter shorthand code for the check.
    pub fn code(&self) -> &'static CheckCode {
        match self {
            // pycodestyle errors
            CheckKind::AmbiguousClassName(_) => &CheckCode::E742,
            CheckKind::AmbiguousFunctionName(_) => &CheckCode::E743,
            CheckKind::AmbiguousVariableName(_) => &CheckCode::E741,
            CheckKind::AssertTuple => &CheckCode::F631,
            CheckKind::BreakOutsideLoop => &CheckCode::F701,
            CheckKind::ContinueOutsideLoop => &CheckCode::F702,
            CheckKind::DefaultExceptNotLast => &CheckCode::F707,
            CheckKind::DoNotAssignLambda => &CheckCode::E731,
            CheckKind::DoNotUseBareExcept => &CheckCode::E722,
            CheckKind::DuplicateArgumentName => &CheckCode::F831,
            CheckKind::FStringMissingPlaceholders => &CheckCode::F541,
            CheckKind::ForwardAnnotationSyntaxError(_) => &CheckCode::F722,
            CheckKind::FutureFeatureNotDefined(_) => &CheckCode::F407,
            CheckKind::IOError(_) => &CheckCode::E902,
            CheckKind::IfTuple => &CheckCode::F634,
            CheckKind::ImportShadowedByLoopVar(_, _) => &CheckCode::F402,
            CheckKind::ImportStarNotPermitted(_) => &CheckCode::F406,
            CheckKind::ImportStarUsage(_, _) => &CheckCode::F405,
            CheckKind::ImportStarUsed(_) => &CheckCode::F403,
            CheckKind::InvalidPrintSyntax => &CheckCode::F633,
            CheckKind::IsLiteral => &CheckCode::F632,
            CheckKind::LateFutureImport => &CheckCode::F404,
            CheckKind::LineTooLong(_, _) => &CheckCode::E501,
            CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
            CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
            CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
            CheckKind::NoneComparison(_) => &CheckCode::E711,
            CheckKind::NotInTest => &CheckCode::E713,
            CheckKind::NotIsTest => &CheckCode::E714,
            CheckKind::RaiseNotImplemented => &CheckCode::F901,
            CheckKind::ReturnOutsideFunction => &CheckCode::F706,
            CheckKind::SyntaxError(_) => &CheckCode::E999,
            CheckKind::ExpressionsInStarAssignment => &CheckCode::F621,
            CheckKind::TrueFalseComparison(_, _) => &CheckCode::E712,
            CheckKind::TwoStarredExpressions => &CheckCode::F622,
            CheckKind::TypeComparison => &CheckCode::E721,
            CheckKind::UndefinedExport(_) => &CheckCode::F822,
            CheckKind::UndefinedLocal(_) => &CheckCode::F823,
            CheckKind::UndefinedName(_) => &CheckCode::F821,
            CheckKind::UnusedImport(_) => &CheckCode::F401,
            CheckKind::UnusedVariable(_) => &CheckCode::F841,
            CheckKind::YieldOutsideFunction => &CheckCode::F704,
            // pycodestyle warnings
            CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
            // flake8-builtins
            CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
            CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
            CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
            // flake8-bugbear
            CheckKind::DoNotAssertFalse => &CheckCode::B011,
            CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
            CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
            // flake8-comprehensions
            CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
            CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
            CheckKind::UnnecessaryGeneratorDict => &CheckCode::C402,
            CheckKind::UnnecessaryListComprehensionSet => &CheckCode::C403,
            CheckKind::UnnecessaryListComprehensionDict => &CheckCode::C404,
            CheckKind::UnnecessaryLiteralSet(_) => &CheckCode::C405,
            CheckKind::UnnecessaryLiteralDict(_) => &CheckCode::C406,
            CheckKind::UnnecessaryCollectionCall(_) => &CheckCode::C408,
            CheckKind::UnnecessaryLiteralWithinTupleCall(..) => &CheckCode::C409,
            CheckKind::UnnecessaryLiteralWithinListCall(..) => &CheckCode::C410,
            CheckKind::UnnecessaryListCall => &CheckCode::C411,
            CheckKind::UnnecessaryCallAroundSorted(_) => &CheckCode::C413,
            CheckKind::UnnecessaryDoubleCastOrProcess(..) => &CheckCode::C414,
            CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
            CheckKind::UnnecessaryComprehension(..) => &CheckCode::C416,
            CheckKind::UnnecessaryMap(_) => &CheckCode::C417,
            // flake8-print
            CheckKind::PrintFound => &CheckCode::T201,
            CheckKind::PPrintFound => &CheckCode::T203,
            // pyupgrade
            CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
            CheckKind::UnnecessaryAbspath => &CheckCode::U002,
            CheckKind::UselessMetaclassType => &CheckCode::U001,
            CheckKind::DeprecatedUnittestAlias(_, _) => &CheckCode::U005,
            CheckKind::UsePEP585Annotation(_) => &CheckCode::U006,
            CheckKind::UsePEP604Annotation => &CheckCode::U007,
            CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
            CheckKind::SuperCallWithParameters => &CheckCode::U008,
            // pydocstyle
            CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
            CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
            CheckKind::BlankLineBeforeSection(_) => &CheckCode::D411,
            CheckKind::CapitalizeSectionName(_) => &CheckCode::D405,
            CheckKind::DashedUnderlineAfterSection(_) => &CheckCode::D407,
            CheckKind::DocumentAllArguments(_) => &CheckCode::D417,
            CheckKind::EndsInPeriod => &CheckCode::D400,
            CheckKind::EndsInPunctuation => &CheckCode::D415,
            CheckKind::FirstLineCapitalized => &CheckCode::D403,
            CheckKind::FitsOnOneLine => &CheckCode::D200,
            CheckKind::IndentWithSpaces => &CheckCode::D206,
            CheckKind::MagicMethod => &CheckCode::D105,
            CheckKind::MultiLineSummaryFirstLine => &CheckCode::D212,
            CheckKind::MultiLineSummarySecondLine => &CheckCode::D213,
            CheckKind::NewLineAfterLastParagraph => &CheckCode::D209,
            CheckKind::NewLineAfterSectionName(_) => &CheckCode::D406,
            CheckKind::NoBlankLineAfterFunction(_) => &CheckCode::D202,
            CheckKind::NoBlankLineAfterSummary => &CheckCode::D205,
            CheckKind::NoBlankLineBeforeClass(_) => &CheckCode::D211,
            CheckKind::NoBlankLineBeforeFunction(_) => &CheckCode::D201,
            CheckKind::NoBlankLinesBetweenHeaderAndContent(_) => &CheckCode::D412,
            CheckKind::NoOverIndentation => &CheckCode::D208,
            CheckKind::NoSignature => &CheckCode::D402,
            CheckKind::NoSurroundingWhitespace => &CheckCode::D210,
            CheckKind::NoThisPrefix => &CheckCode::D404,
            CheckKind::NoUnderIndentation => &CheckCode::D207,
            CheckKind::NonEmpty => &CheckCode::D419,
            CheckKind::NonEmptySection(_) => &CheckCode::D414,
            CheckKind::OneBlankLineAfterClass(_) => &CheckCode::D204,
            CheckKind::OneBlankLineBeforeClass(_) => &CheckCode::D203,
            CheckKind::PublicClass => &CheckCode::D101,
            CheckKind::PublicFunction => &CheckCode::D103,
            CheckKind::PublicInit => &CheckCode::D107,
            CheckKind::PublicMethod => &CheckCode::D102,
            CheckKind::PublicModule => &CheckCode::D100,
            CheckKind::PublicNestedClass => &CheckCode::D106,
            CheckKind::PublicPackage => &CheckCode::D104,
            CheckKind::SectionNameEndsInColon(_) => &CheckCode::D416,
            CheckKind::SectionNotOverIndented(_) => &CheckCode::D214,
            CheckKind::SectionUnderlineAfterName(_) => &CheckCode::D408,
            CheckKind::SectionUnderlineMatchesSectionLength(_) => &CheckCode::D409,
            CheckKind::SectionUnderlineNotOverIndented(_) => &CheckCode::D215,
            CheckKind::SkipDocstring => &CheckCode::D418,
            CheckKind::UsesTripleQuotes => &CheckCode::D300,
            // Meta
            CheckKind::UnusedNOQA(_) => &CheckCode::M001,
        }
    }

    /// The body text for the check.
    pub fn body(&self) -> String {
        match self {
            // pycodestyle errors
            CheckKind::AmbiguousClassName(name) => {
                format!("Ambiguous class name: `{}`", name)
            }
            CheckKind::AmbiguousFunctionName(name) => {
                format!("Ambiguous function name: `{}`", name)
            }
            CheckKind::AmbiguousVariableName(name) => {
                format!("Ambiguous variable name: `{}`", name)
            }
            CheckKind::AssertTuple => {
                "Assert test is a non-empty tuple, which is always `True`".to_string()
            }
            CheckKind::BreakOutsideLoop => "`break` outside loop".to_string(),
            CheckKind::ContinueOutsideLoop => "`continue` not properly in loop".to_string(),
            CheckKind::DefaultExceptNotLast => {
                "An `except:` block as not the last exception handler".to_string()
            }
            CheckKind::DoNotAssignLambda => {
                "Do not assign a lambda expression, use a def".to_string()
            }
            CheckKind::DoNotUseBareExcept => "Do not use bare `except`".to_string(),
            CheckKind::DuplicateArgumentName => {
                "Duplicate argument name in function definition".to_string()
            }
            CheckKind::ForwardAnnotationSyntaxError(body) => {
                format!("Syntax error in forward annotation: `{body}`")
            }
            CheckKind::FStringMissingPlaceholders => {
                "f-string without any placeholders".to_string()
            }
            CheckKind::FutureFeatureNotDefined(name) => {
                format!("Future feature `{name}` is not defined")
            }
            CheckKind::IOError(message) => message.clone(),
            CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
            CheckKind::InvalidPrintSyntax => {
                "Use of `>>` is invalid with `print` function".to_string()
            }
            CheckKind::ImportShadowedByLoopVar(name, line) => {
                format!("Import `{name}` from line {line} shadowed by loop variable")
            }
            CheckKind::ImportStarNotPermitted(name) => {
                format!("`from {name} import *` only allowed at module level")
            }
            CheckKind::ImportStarUsed(name) => {
                format!("`from {name} import *` used; unable to detect undefined names")
            }
            CheckKind::ImportStarUsage(name, sources) => {
                let sources = sources
                    .iter()
                    .map(|source| format!("`{}`", source))
                    .join(", ");
                format!("`{name}` may be undefined, or defined from star imports: {sources}")
            }
            CheckKind::IsLiteral => "Use `==` and `!=` to compare constant literals".to_string(),
            CheckKind::LateFutureImport => {
                "`from __future__` imports must occur at the beginning of the file".to_string()
            }
            CheckKind::LineTooLong(length, limit) => {
                format!("Line too long ({length} > {limit} characters)")
            }
            CheckKind::ModuleImportNotAtTopOfFile => {
                "Module level import not at top of file".to_string()
            }
            CheckKind::MultiValueRepeatedKeyLiteral => {
                "Dictionary key literal repeated".to_string()
            }
            CheckKind::MultiValueRepeatedKeyVariable(name) => {
                format!("Dictionary key `{name}` repeated")
            }
            CheckKind::NoneComparison(op) => match op {
                RejectedCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(),
                RejectedCmpop::NotEq => {
                    "Comparison to `None` should be `cond is not None`".to_string()
                }
            },
            CheckKind::NotInTest => "Test for membership should be `not in`".to_string(),
            CheckKind::NotIsTest => "Test for object identity should be `is not`".to_string(),
            CheckKind::RaiseNotImplemented => {
                "`raise NotImplemented` should be `raise NotImplementedError`".to_string()
            }
            CheckKind::ReturnOutsideFunction => {
                "`return` statement outside of a function/method".to_string()
            }
            CheckKind::SyntaxError(message) => format!("SyntaxError: {message}"),
            CheckKind::ExpressionsInStarAssignment => {
                "Too many expressions in star-unpacking assignment".to_string()
            }
            CheckKind::TrueFalseComparison(value, op) => match *value {
                true => match op {
                    RejectedCmpop::Eq => {
                        "Comparison to `True` should be `cond is True`".to_string()
                    }
                    RejectedCmpop::NotEq => {
                        "Comparison to `True` should be `cond is not True`".to_string()
                    }
                },
                false => match op {
                    RejectedCmpop::Eq => {
                        "Comparison to `False` should be `cond is False`".to_string()
                    }
                    RejectedCmpop::NotEq => {
                        "Comparison to `False` should be `cond is not False`".to_string()
                    }
                },
            },
            CheckKind::TwoStarredExpressions => "Two starred expressions in assignment".to_string(),
            CheckKind::TypeComparison => "Do not compare types, use `isinstance()`".to_string(),
            CheckKind::UndefinedExport(name) => {
                format!("Undefined name `{name}` in `__all__`")
            }
            CheckKind::UndefinedLocal(name) => {
                format!("Local variable `{name}` referenced before assignment")
            }
            CheckKind::UndefinedName(name) => {
                format!("Undefined name `{name}`")
            }
            CheckKind::UnusedImport(names) => {
                let names = names.iter().map(|name| format!("`{name}`")).join(", ");
                format!("{names} imported but unused")
            }
            CheckKind::UnusedVariable(name) => {
                format!("Local variable `{name}` is assigned to but never used")
            }
            CheckKind::YieldOutsideFunction => {
                "`yield` or `yield from` statement outside of a function/method".to_string()
            }
            // pycodestyle warnings
            CheckKind::NoNewLineAtEndOfFile => "No newline at end of file".to_string(),
            // flake8-builtins
            CheckKind::BuiltinVariableShadowing(name) => {
                format!("Variable `{name}` is shadowing a python builtin")
            }
            CheckKind::BuiltinArgumentShadowing(name) => {
                format!("Argument `{name}` is shadowing a python builtin")
            }
            CheckKind::BuiltinAttributeShadowing(name) => {
                format!("Class attribute `{name}` is shadowing a python builtin")
            }
            // flake8-bugbear
            CheckKind::DoNotAssertFalse => {
                "Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`"
                    .to_string()
            }
            CheckKind::DuplicateHandlerException(names) => {
                if names.len() == 1 {
                    let name = &names[0];
                    format!("Exception handler with duplicate exception: `{name}`")
                } else {
                    let names = names.iter().map(|name| format!("`{name}`")).join(", ");
                    format!("Exception handler with duplicate exceptions: {names}")
                }
            }
            CheckKind::DuplicateTryBlockException(name) => {
                format!("try-except block with duplicate exception `{name}`")
            }
            // flake8-comprehensions
            CheckKind::UnnecessaryGeneratorList => {
                "Unnecessary generator - rewrite as a list comprehension".to_string()
            }
            CheckKind::UnnecessaryGeneratorSet => {
                "Unnecessary generator - rewrite as a set comprehension".to_string()
            }
            CheckKind::UnnecessaryGeneratorDict => {
                "Unnecessary generator - rewrite as a dict comprehension".to_string()
            }
            CheckKind::UnnecessaryListComprehensionSet => {
                "Unnecessary list comprehension - rewrite as a set comprehension".to_string()
            }
            CheckKind::UnnecessaryListComprehensionDict => {
                "Unnecessary list comprehension - rewrite as a dict comprehension".to_string()
            }
            CheckKind::UnnecessaryLiteralSet(obj_type) => {
                format!("Unnecessary {obj_type} literal - rewrite as a set literal")
            }
            CheckKind::UnnecessaryLiteralDict(obj_type) => {
                format!("Unnecessary {obj_type} literal - rewrite as a dict literal")
            }
            CheckKind::UnnecessaryCollectionCall(obj_type) => {
                format!("Unnecessary {obj_type} call - rewrite as a literal")
            }
            CheckKind::UnnecessaryLiteralWithinTupleCall(literal) => {
                if literal == "list" {
                    format!(
                        "Unnecessary {literal} literal passed to tuple() - rewrite as a tuple literal"
                    )
                } else {
                    format!(
                        "Unnecessary {literal} literal passed to tuple() - remove the outer call to tuple()"
                    )
                }
            }
            CheckKind::UnnecessaryLiteralWithinListCall(literal) => {
                if literal == "list" {
                    format!(
                        "Unnecessary {literal} literal passed to list() - remove the outer call to list()"
                    )
                } else {
                    format!(
                        "Unnecessary {literal} literal passed to list() - rewrite as a list literal"
                    )
                }
            }
            CheckKind::UnnecessaryListCall => {
                "Unnecessary list call - remove the outer call to list()".to_string()
            }
            CheckKind::UnnecessaryCallAroundSorted(func) => {
                format!("Unnecessary {func} call around sorted()")
            }
            CheckKind::UnnecessaryDoubleCastOrProcess(inner, outer) => {
                format!("Unnecessary {inner} call within {outer}().")
            }
            CheckKind::UnnecessarySubscriptReversal(func) => {
                format!("Unnecessary subscript reversal of iterable within {func}()")
            }
            CheckKind::UnnecessaryComprehension(obj_type) => {
                format!(" Unnecessary {obj_type} comprehension - rewrite using {obj_type}()")
            }
            CheckKind::UnnecessaryMap(obj_type) => {
                if obj_type == "generator" {
                    "Unnecessary map usage - rewrite using a generator expression".to_string()
                } else {
                    format!("Unnecessary map usage - rewrite using a {obj_type} comprehension")
                }
            }
            // flake8-print
            CheckKind::PrintFound => "`print` found".to_string(),
            CheckKind::PPrintFound => "`pprint` found".to_string(),
            // pyupgrade
            CheckKind::TypeOfPrimitive(primitive) => {
                format!("Use `{}` instead of `type(...)`", primitive.builtin())
            }
            CheckKind::UnnecessaryAbspath => {
                "`abspath(__file__)` is unnecessary in Python 3.9 and later".to_string()
            }
            CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
            CheckKind::DeprecatedUnittestAlias(alias, target) => {
                format!("`{}` is deprecated, use `{}` instead", alias, target)
            }
            CheckKind::UselessObjectInheritance(name) => {
                format!("Class `{name}` inherits from object")
            }
            CheckKind::UsePEP585Annotation(name) => {
                format!(
                    "Use `{}` instead of `{}` for type annotations",
                    name.to_lowercase(),
                    name,
                )
            }
            CheckKind::UsePEP604Annotation => "Use `X | Y` for type annotations".to_string(),
            CheckKind::SuperCallWithParameters => {
                "Use `super()` instead of `super(__class__, self)`".to_string()
            }
            // pydocstyle
            CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
            CheckKind::NoBlankLineAfterSummary => {
                "1 blank line required between summary line and description".to_string()
            }
            CheckKind::NewLineAfterLastParagraph => {
                "Multi-line docstring closing quotes should be on a separate line".to_string()
            }
            CheckKind::NoSurroundingWhitespace => {
                "No whitespaces allowed surrounding docstring text".to_string()
            }
            CheckKind::EndsInPeriod => "First line should end with a period".to_string(),
            CheckKind::NonEmpty => "Docstring is empty".to_string(),
            CheckKind::EndsInPunctuation => {
                "First line should end with a period, question mark, or exclamation point"
                    .to_string()
            }
            CheckKind::FirstLineCapitalized => {
                "First word of the first line should be properly capitalized".to_string()
            }
            CheckKind::UsesTripleQuotes => r#"Use """triple double quotes""""#.to_string(),
            CheckKind::MultiLineSummaryFirstLine => {
                "Multi-line docstring summary should start at the first line".to_string()
            }
            CheckKind::MultiLineSummarySecondLine => {
                "Multi-line docstring summary should start at the second line".to_string()
            }
            CheckKind::NoSignature => {
                "First line should not be the function's 'signature'".to_string()
            }
            CheckKind::NoBlankLineBeforeFunction(num_lines) => {
                format!("No blank lines allowed before function docstring (found {num_lines})")
            }
            CheckKind::NoBlankLineAfterFunction(num_lines) => {
                format!("No blank lines allowed after function docstring (found {num_lines})")
            }
            CheckKind::NoBlankLineBeforeClass(_) => {
                "No blank lines allowed before class docstring".to_string()
            }
            CheckKind::OneBlankLineBeforeClass(_) => {
                "1 blank line required before class docstring".to_string()
            }
            CheckKind::OneBlankLineAfterClass(_) => {
                "1 blank line required after class docstring".to_string()
            }
            CheckKind::PublicModule => "Missing docstring in public module".to_string(),
            CheckKind::PublicClass => "Missing docstring in public class".to_string(),
            CheckKind::PublicMethod => "Missing docstring in public method".to_string(),
            CheckKind::PublicFunction => "Missing docstring in public function".to_string(),
            CheckKind::PublicPackage => "Missing docstring in public package".to_string(),
            CheckKind::MagicMethod => "Missing docstring in magic method".to_string(),
            CheckKind::PublicNestedClass => "Missing docstring in public nested class".to_string(),
            CheckKind::PublicInit => "Missing docstring in `__init__`".to_string(),
            CheckKind::NoThisPrefix => {
                "First word of the docstring should not be `This`".to_string()
            }
            CheckKind::SkipDocstring => {
                "Function decorated with @overload shouldn't contain a docstring".to_string()
            }
            CheckKind::CapitalizeSectionName(name) => {
                format!("Section name should be properly capitalized (\"{name}\")")
            }
            CheckKind::BlankLineAfterLastSection(name) => {
                format!("Missing blank line after last section (\"{name}\")")
            }
            CheckKind::BlankLineAfterSection(name) => {
                format!("Missing blank line after section (\"{name}\")")
            }
            CheckKind::BlankLineBeforeSection(name) => {
                format!("Missing blank line before section (\"{name}\")")
            }
            CheckKind::NewLineAfterSectionName(name) => {
                format!("Section name should end with a newline (\"{name}\")")
            }
            CheckKind::DashedUnderlineAfterSection(name) => {
                format!("Missing dashed underline after section (\"{name}\")")
            }
            CheckKind::SectionUnderlineAfterName(name) => {
                format!("Section underline should be in the line following the section's name (\"{name}\")")
            }
            CheckKind::SectionUnderlineMatchesSectionLength(name) => {
                format!("Section underline should match the length of its name (\"{name}\")")
            }
            CheckKind::NoBlankLinesBetweenHeaderAndContent(name) => {
                format!(
                    "No blank lines allowed between a section header and its content (\"{name}\")"
                )
            }
            CheckKind::NonEmptySection(name) => format!("Section has no content (\"{name}\")"),
            CheckKind::SectionNotOverIndented(name) => {
                format!("Section is over-indented (\"{name}\")")
            }
            CheckKind::SectionUnderlineNotOverIndented(name) => {
                format!("Section underline is over-indented (\"{name}\")")
            }
            CheckKind::SectionNameEndsInColon(name) => {
                format!("Section name should end with a colon (\"{name}\")")
            }
            CheckKind::DocumentAllArguments(names) => {
                if names.len() == 1 {
                    let name = &names[0];
                    format!("Missing argument description in the docstring: `{name}`")
                } else {
                    let names = names.iter().map(|name| format!("`{name}`")).join(", ");
                    format!("Missing argument descriptions in the docstring: {names}")
                }
            }
            CheckKind::IndentWithSpaces => {
                "Docstring should be indented with spaces, not tabs".to_string()
            }
            CheckKind::NoUnderIndentation => "Docstring is under-indented".to_string(),
            CheckKind::NoOverIndentation => "Docstring is over-indented".to_string(),
            // Meta
            CheckKind::UnusedNOQA(codes) => match codes {
                None => "Unused `noqa` directive".to_string(),
                Some(codes) => {
                    let codes = codes
                        .iter()
                        .map(|code| {
                            if CheckCode::from_str(code).is_ok() {
                                code.to_string()
                            } else {
                                format!("{code} (not implemented)")
                            }
                        })
                        .join(", ");
                    format!("Unused `noqa` directive for: {codes}")
                }
            },
        }
    }

    /// Whether the check kind is (potentially) fixable.
    pub fn fixable(&self) -> bool {
        matches!(
            self,
            CheckKind::DeprecatedUnittestAlias(_, _)
                | CheckKind::DoNotAssertFalse
                | CheckKind::DuplicateHandlerException(_)
                | CheckKind::PPrintFound
                | CheckKind::PrintFound
                | CheckKind::SuperCallWithParameters
                | CheckKind::TypeOfPrimitive(_)
                | CheckKind::UnnecessaryAbspath
                | CheckKind::UnusedImport(_)
                | CheckKind::UnusedNOQA(_)
                | CheckKind::UselessMetaclassType
                | CheckKind::UselessObjectInheritance(_)
                | CheckKind::UsePEP585Annotation(_)
                | CheckKind::UsePEP604Annotation
        )
    }
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fix {
    pub content: String,
    pub location: Location,
    pub end_location: Location,
    pub applied: bool,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Check {
    pub kind: CheckKind,
    pub location: Location,
    pub end_location: Location,
    pub fix: Option<Fix>,
}

impl Check {
    pub fn new(kind: CheckKind, span: Range) -> Self {
        Self {
            kind,
            location: span.location,
            end_location: span.end_location,
            fix: None,
        }
    }

    pub fn amend(&mut self, fix: Fix) {
        self.fix = Some(fix);
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use anyhow::Result;
    use strum::IntoEnumIterator;

    use crate::checks::CheckCode;

    #[test]
    fn check_code_serialization() -> Result<()> {
        for check_code in CheckCode::iter() {
            assert!(
                CheckCode::from_str(check_code.as_ref()).is_ok(),
                "{:?} could not be round-trip serialized.",
                check_code
            );
        }
        Ok(())
    }
}
