use std::fmt::Display;

use nom::error::{self, ParseError};
use nom::Parser;

use crate::{CowSpan, IResult, Span};

pub trait NomErrorExt<E> {
    fn is_failure(&self) -> bool;
    fn map_err<O: FnOnce(E) -> E>(self, op: O) -> nom::Err<E>;
    fn map_fail<O: FnOnce(E) -> E>(self, op: O) -> nom::Err<E>;
}

impl<E> NomErrorExt<E> for nom::Err<E> {
    fn is_failure(&self) -> bool {
        matches!(self, Self::Failure(_))
    }

    fn map_err<O: FnOnce(E) -> E>(self, op: O) -> nom::Err<E> {
        match self {
            e @ Self::Failure(_) => e,
            e => e.map(op),
        }
    }

    fn map_fail<O: FnOnce(E) -> E>(self, op: O) -> nom::Err<E> {
        match self {
            e @ Self::Error(_) => e,
            e => e.map(op),
        }
    }
}

/// cut a parser and map the error
pub fn cut_with_err<'a, O>(
    mut parser: impl FnMut(Span<'a>) -> IResult<'a, O>,
    mut with: impl FnMut(Error<'a>) -> Error<'a>,
) -> impl FnMut(Span<'a>) -> IResult<'a, O> {
    move |input| match parser.parse(input) {
        Err(nom::Err::Error(e)) => Err(nom::Err::Failure(with(e))),
        rest => rest,
    }
}

pub trait IResultExt<'a> {
    fn map_cut(self, kind: ErrorKind<'a>) -> Self;
}

impl<'a, T> IResultExt<'a> for IResult<'a, T> {
    fn map_cut(self, kind: ErrorKind<'a>) -> Self {
        self.map_err(move |e: nom::Err<Error<'a>>| {
            let input = match e {
                nom::Err::Incomplete(_) => return e,
                nom::Err::Error(e) => e.context().clone(),
                nom::Err::Failure(e) => e.context().clone(),
            };
            Error::failure_from_kind(input, kind)
        })
    }
}

#[derive(Debug)]
pub struct Error<'a> {
    context: CowSpan<'a>,
    kind: ErrorKind<'a>,
}

#[derive(Debug)]
pub enum ExpectedValueKind {
    ReservedKeyword,
    Other,
}

#[derive(Debug)]
pub enum ErrorKind<'a> {
    Foreign,
    ReservedGeo(&'a str),
    GeoRadius,
    GeoRadiusArgumentCount(usize),
    GeoBoundingBox,
    GeoPolygon,
    GeoPolygonNotEnoughPoints(usize),
    GeoCoordinatesNotPair(usize),
    MisusedGeoRadius,
    MisusedGeoBoundingBox,
    VectorFilterLeftover,
    VectorFilterInvalidQuotes,
    VectorFilterMissingEmbedder,
    VectorFilterInvalidEmbedder,
    VectorFilterMissingFragment,
    VectorFilterInvalidFragment,
    VectorFilterUnknownSuffix(Option<&'static str>, String),
    VectorFilterOperation,
    InvalidPrimary,
    InvalidEscapedNumber,
    ExpectedEof,
    ExpectedValue(ExpectedValueKind),
    MalformedValue,
    InOpeningBracket,
    InClosingBracket,
    NonFiniteFloat,
    InExpectedValue(ExpectedValueKind),
    ReservedKeyword(String),
    MissingClosingDelimiter(char),
    Char(char),
    InternalError(error::ErrorKind),
    DepthLimitReached,
    External(String),
}

impl<'a> Error<'a> {
    pub fn kind(&self) -> &ErrorKind<'a> {
        &self.kind
    }

    pub fn context(&self) -> &CowSpan<'a> {
        &self.context
    }

    pub fn new_from_kind(context: CowSpan<'a>, kind: ErrorKind<'a>) -> Self {
        Self { context, kind }
    }

    pub fn failure_from_kind(context: CowSpan<'a>, kind: ErrorKind<'a>) -> nom::Err<Self> {
        nom::Err::Failure(Self::new_from_kind(context, kind))
    }

    pub fn new_from_external(context: CowSpan<'a>, error: impl std::error::Error) -> Self {
        Self::new_from_kind(context, ErrorKind::External(error.to_string()))
    }

    pub fn char(self) -> char {
        match self.kind {
            ErrorKind::Char(c) => c,
            error => panic!("Internal filter parser error: {:?}", error),
        }
    }
}

impl<'a> ParseError<Span<'a>> for Error<'a> {
    fn from_error_kind(input: Span<'a>, kind: error::ErrorKind) -> Self {
        let kind = match kind {
            error::ErrorKind::Eof => ErrorKind::ExpectedEof,
            kind => ErrorKind::InternalError(kind),
        };
        Self { context: input.into(), kind }
    }

    fn append(_input: Span<'a>, _kind: error::ErrorKind, other: Self) -> Self {
        other
    }

    fn from_char(input: Span<'a>, c: char) -> Self {
        Self { context: input.into(), kind: ErrorKind::Char(c) }
    }
}

impl Display for Error<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let input = self.context.fragment();
        // When printing our error message we want to escape all `\n` to be sure we keep our format with the
        // first line being the diagnostic and the second line being the incriminated filter.
        let escaped_input = input.escape_debug();

        fn key_suggestion<'a>(key: &str, keys: &[&'a str]) -> Option<&'a str> {
            let typos =
                levenshtein_automata::LevenshteinAutomatonBuilder::new(2, true).build_dfa(key);
            for key in keys.iter() {
                match typos.eval(key) {
                    levenshtein_automata::Distance::Exact(_) => {
                        return Some(key);
                    }
                    levenshtein_automata::Distance::AtLeast(_) => continue,
                }
            }
            None
        }

        match &self.kind {
            ErrorKind::ExpectedValue(_) if input.trim().is_empty() => {
                writeln!(f, "Was expecting a value but instead got nothing.")?
            }
            ErrorKind::ExpectedValue(ExpectedValueKind::ReservedKeyword) => {
                writeln!(f, "Was expecting a value but instead got `{escaped_input}`, which is a reserved keyword. To use `{escaped_input}` as a field name or a value, surround it by quotes.")?
            }
            ErrorKind::ExpectedValue(ExpectedValueKind::Other) => {
                writeln!(f, "Was expecting a value but instead got `{}`.", escaped_input)?
            }
            ErrorKind::MalformedValue => {
                writeln!(f, "Malformed value: `{}`.", escaped_input)?
            }
            ErrorKind::MissingClosingDelimiter(c) => {
                writeln!(f, "Expression `{}` is missing the following closing delimiter: `{}`.", escaped_input, c)?
            }
            ErrorKind::InvalidPrimary => {
                let text = if input.trim().is_empty() { "but instead got nothing.".to_string() } else { format!("at `{}`.", escaped_input) };
                writeln!(f, "Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `IN`, `NOT IN`, `TO`, `EXISTS`, `NOT EXISTS`, `IS NULL`, `IS NOT NULL`, `IS EMPTY`, `IS NOT EMPTY`, `CONTAINS`, `NOT CONTAINS`, `STARTS WITH`, `NOT STARTS WITH`, `_geoRadius`, `_geoBoundingBox` or `_geoPolygon` {text}")?
            }
            ErrorKind::InvalidEscapedNumber => {
                writeln!(f, "Found an invalid escaped sequence number: `{}`.", escaped_input)?
            }
            ErrorKind::ExpectedEof => {
                writeln!(f, "Found unexpected characters at the end of the filter: `{}`. You probably forgot an `OR` or an `AND` rule.", escaped_input)?
            }
            ErrorKind::GeoRadius => {
                writeln!(f, "The `_geoRadius` filter must be in the form: `_geoRadius(latitude, longitude, radius, optionalResolution)`.")?
            }
            ErrorKind::GeoRadiusArgumentCount(count) => {
                writeln!(f, "Was expecting 3 or 4 arguments for `_geoRadius`, but instead found {count}.")?
            }
            ErrorKind::GeoBoundingBox => {
                writeln!(f, "The `_geoBoundingBox` filter expects two pairs of arguments: `_geoBoundingBox([latitude, longitude], [latitude, longitude])`.")?
            }
            ErrorKind::GeoPolygon => {
                writeln!(f, "The `_geoPolygon` filter doesn't match the expected format: `_geoPolygon([latitude, longitude], [latitude, longitude])`.")?
            }
            ErrorKind::GeoPolygonNotEnoughPoints(n) => {
                writeln!(f, "The `_geoPolygon` filter expects at least 3 points but only {n} were specified")?;
            }
            ErrorKind::GeoCoordinatesNotPair(number) => {
                writeln!(f, "Was expecting 2 coordinates but instead found {number}.")?
            }
            ErrorKind::ReservedGeo(name) => {
                writeln!(f, "`{}` is a reserved keyword and thus can't be used as a filter expression. Use the `_geoRadius(latitude, longitude, distance)` or `_geoBoundingBox([latitude, longitude], [latitude, longitude])` built-in rules to filter on `_geo` coordinates.", name.escape_debug())?
            }
            ErrorKind::MisusedGeoRadius => {
                writeln!(f, "The `_geoRadius` filter is an operation and can't be used as a value.")?
            }
            ErrorKind::MisusedGeoBoundingBox => {
                writeln!(f, "The `_geoBoundingBox` filter is an operation and can't be used as a value.")?
            }
            ErrorKind::VectorFilterLeftover => {
                writeln!(f, "The vector filter has leftover tokens.")?
            }
            ErrorKind::VectorFilterUnknownSuffix(_, value) if value.as_str() == "." => {
                writeln!(f, "Was expecting one of `.fragments`, `.userProvided`, `.documentTemplate`, `.regenerate` or nothing, but instead found a point without a valid value.")?;
            }
            ErrorKind::VectorFilterUnknownSuffix(None, value) if ["fragments", "userProvided", "documentTemplate", "regenerate"].contains(&value.as_str()) => {
                // This will happen with "_vectors.rest.\"userProvided\"" for instance
                writeln!(f, "Was expecting this part to be unquoted.")?
            }
            ErrorKind::VectorFilterUnknownSuffix(None, value) => {
                if let Some(suggestion) = key_suggestion(value, &["fragments", "userProvided", "documentTemplate", "regenerate"]) {
                    writeln!(f, "Was expecting one of `fragments`, `userProvided`, `documentTemplate`, `regenerate` or nothing, but instead found `{value}`. Did you mean `{suggestion}`?")?;
                } else {
                    writeln!(f, "Was expecting one of `fragments`, `userProvided`, `documentTemplate`, `regenerate` or nothing, but instead found `{value}`.")?;
                }
            }
            ErrorKind::VectorFilterUnknownSuffix(Some(previous_filter_kind), value) => {
                writeln!(f, "Vector filter can only accept one of `fragments`, `userProvided`, `documentTemplate` or `regenerate`, but found both `{previous_filter_kind}` and `{value}`.")?
            },
            ErrorKind::VectorFilterInvalidFragment => {
                writeln!(f, "The vector filter's fragment name is invalid.")?
            }
            ErrorKind::VectorFilterMissingFragment => {
                writeln!(f, "The vector filter is missing a fragment name.")?
            }
            ErrorKind::VectorFilterMissingEmbedder => {
                writeln!(f, "Was expecting embedder name but found nothing.")?
            }
            ErrorKind::VectorFilterInvalidEmbedder => {
                writeln!(f, "The vector filter's embedder name is invalid.")?
            }
            ErrorKind::VectorFilterOperation => {
                writeln!(f, "Was expecting an operation like `EXISTS` or `NOT EXISTS` after the vector filter.")?
            }
            ErrorKind::VectorFilterInvalidQuotes => {
                writeln!(f, "The quotes in one of the values are inconsistent.")?
            }
            ErrorKind::ReservedKeyword(word) => {
                writeln!(f, "`{word}` is a reserved keyword and thus cannot be used as a field name unless it is put inside quotes. Use \"{word}\" or \'{word}\' instead.")?
            }
            ErrorKind::InOpeningBracket => {
                writeln!(f, "Expected `[` after `IN` keyword.")?
            }
            ErrorKind::InClosingBracket => {
                writeln!(f, "Expected matching `]` after the list of field names given to `IN[`")?
            }
            ErrorKind::NonFiniteFloat => {
                writeln!(f, "Non finite floats are not supported")?
            }
            ErrorKind::InExpectedValue(ExpectedValueKind::ReservedKeyword) => {
                writeln!(f, "Expected only comma-separated field names inside `IN[..]` but instead found `{escaped_input}`, which is a keyword. To use `{escaped_input}` as a field name or a value, surround it by quotes.")?
            }
            ErrorKind::InExpectedValue(ExpectedValueKind::Other) => {
                writeln!(f, "Expected only comma-separated field names inside `IN[..]` but instead found `{escaped_input}`.")?
            }
            ErrorKind::Char(c) => {
                panic!("Tried to display a char error with `{}`", c)
            }
            ErrorKind::DepthLimitReached => writeln!(
                f,
                "The filter exceeded the maximum depth limit. Try rewriting the filter so that it contains fewer nested conditions."
            )?,
            ErrorKind::InternalError(kind) => writeln!(
                f,
                "Encountered an internal `{:?}` error while parsing your filter. Please fill an issue", kind
            )?,
            ErrorKind::External(ref error) => writeln!(f, "{}", error)?,
            ErrorKind::Foreign => writeln!(f, "Was expecting a field name and an condition inside `_foreign(..)` filter but instead found `{escaped_input}`.")?,
        }
        if let Some(base_column) = self.context.get_utf8_column() {
            let size = self.context.fragment().chars().count();
            write!(f, "{}:{} {}", base_column, base_column + size, self.context.extra())
        } else {
            write!(f, "{}", self.context.extra())
        }
    }
}
