use std::io::{self, BufWriter, Write};

use anyhow::Result;
use serde::{Serialize, Serializer, ser::SerializeSeq};

use ty_python_semantic::default_lint_registry;
use ty_python_semantic::lint::{Level, LintId, LintStatus};

use crate::args::HelpFormat;

#[derive(Serialize)]
struct Explanation<'a> {
    name: &'a str,
    summary: &'a str,
    documentation: String,
    default_level: Level,
    status: LintStatus,
}

impl<'a> Explanation<'a> {
    fn from_lint(lint: &'a LintId) -> Self {
        Self {
            name: lint.name().as_str(),
            summary: lint.summary(),
            documentation: lint.documentation(),
            default_level: lint.default_level(),
            status: *lint.status(),
        }
    }
}

impl std::fmt::Display for Explanation<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "# {}\n", self.name)?;

        let status = match self.status {
            LintStatus::Stable { since } => format!("Stable (since {since})"),
            LintStatus::Deprecated { since, reason } => {
                format!("Deprecated (since {since}): {reason}")
            }
            LintStatus::Removed { since, reason } => format!("Removed (since {since}): {reason}"),
        };

        writeln!(f, "Default level: {} | {status}\n", self.default_level)?;

        f.write_str(self.documentation.trim())
    }
}

/// Explain a single rule.
pub(crate) fn rule(name: &str, format: HelpFormat) -> Result<()> {
    let registry = default_lint_registry();
    let lint = registry.get(name).map_err(|e| anyhow::anyhow!("{e}"))?;

    let mut stdout = BufWriter::new(io::stdout().lock());
    match format {
        HelpFormat::Text => {
            writeln!(stdout, "{}", Explanation::from_lint(&lint))?;
        }
        HelpFormat::Json => {
            serde_json::to_writer_pretty(&mut stdout, &Explanation::from_lint(&lint))?;
        }
    }
    Ok(())
}

/// Explain all rules.
pub(crate) fn rules(format: HelpFormat) -> Result<()> {
    let registry = default_lint_registry();
    let mut lints: Vec<LintId> = registry.lints().to_vec();
    lints.sort_by_key(|l| l.name());

    let mut stdout = BufWriter::new(io::stdout().lock());
    match format {
        HelpFormat::Text => {
            for lint in lints {
                writeln!(stdout, "{}\n", Explanation::from_lint(&lint))?;
            }
        }
        HelpFormat::Json => {
            let mut serializer = serde_json::Serializer::pretty(stdout);
            let mut seq = serializer.serialize_seq(None)?;
            for lint in lints {
                seq.serialize_element(&Explanation::from_lint(&lint))?;
            }
            seq.end()?;
        }
    }
    Ok(())
}
