use rudof_rdf::rdf_core::query::QueryResultFormat;
use std::fmt::{Display, Formatter};
use std::str::FromStr;

use crate::errors::QueryError;

/// Output formats for SPARQL query results supported by Rudof.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ResultQueryFormat {
    /// Internal format - internal representation for processing
    Internal,
    /// Turtle - compact RDF format (for CONSTRUCT/DESCRIBE)
    Turtle,
    /// N-Triples - simple line-based RDF format
    NTriples,
    /// JSON-LD - JSON format for Linked Data (for CONSTRUCT/DESCRIBE)
    JsonLd,
    /// JSON - JSON format for tabular results (for SELECT)
    Json,
    /// RDF/XML - XML-based RDF serialization (for CONSTRUCT/DESCRIBE)
    RdfXml,
    /// CSV - comma-separated values (for SELECT)
    Csv,
    /// Markdown - markdown format (for SELECT)
    Markdown,
    /// TriG - Turtle with named graphs (for CONSTRUCT/DESCRIBE)
    TriG,
    /// Notation3 - superset of Turtle (for CONSTRUCT/DESCRIBE)
    N3,
    /// N-Quads - N-Triples with named graphs (for CONSTRUCT/DESCRIBE)
    NQuads,
}

/// SPARQL query types supported by Rudof.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum QueryType {
    /// SELECT - returns tabular results (variable bindings)
    Select,
    /// CONSTRUCT - returns an RDF graph constructed from query results
    Construct,
    /// ASK - returns a boolean (true/false) result
    Ask,
    /// DESCRIBE - returns an RDF graph describing resources
    Describe,
}

// ============================================================================
// ResultQueryFormat
// ============================================================================

impl Display for ResultQueryFormat {
    fn fmt(&self, dest: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
        match self {
            ResultQueryFormat::Internal => write!(dest, "internal"),
            ResultQueryFormat::Turtle => write!(dest, "turtle"),
            ResultQueryFormat::NTriples => write!(dest, "ntriples"),
            ResultQueryFormat::JsonLd => write!(dest, "json-ld"),
            ResultQueryFormat::Json => write!(dest, "json"),
            ResultQueryFormat::RdfXml => write!(dest, "rdf-xml"),
            ResultQueryFormat::Csv => write!(dest, "csv"),
            ResultQueryFormat::Markdown => write!(dest, "markdown"),
            ResultQueryFormat::TriG => write!(dest, "trig"),
            ResultQueryFormat::N3 => write!(dest, "n3"),
            ResultQueryFormat::NQuads => write!(dest, "nquads"),
        }
    }
}

impl From<ResultQueryFormat> for QueryResultFormat {
    fn from(format: ResultQueryFormat) -> Self {
        match format {
            ResultQueryFormat::Internal => QueryResultFormat::Turtle,
            ResultQueryFormat::Turtle => QueryResultFormat::Turtle,
            ResultQueryFormat::NTriples => QueryResultFormat::NTriples,
            ResultQueryFormat::JsonLd => QueryResultFormat::JsonLd,
            ResultQueryFormat::Json => QueryResultFormat::Json,
            ResultQueryFormat::RdfXml => QueryResultFormat::RdfXml,
            ResultQueryFormat::Csv => QueryResultFormat::Csv,
            ResultQueryFormat::Markdown => QueryResultFormat::Markdown,
            ResultQueryFormat::TriG => QueryResultFormat::TriG,
            ResultQueryFormat::N3 => QueryResultFormat::N3,
            ResultQueryFormat::NQuads => QueryResultFormat::NQuads,
        }
    }
}

impl ResultQueryFormat {
    const FORMATS: &'static [(&'static [&'static str], ResultQueryFormat)] = &[
        (&["internal", "asciitable"], ResultQueryFormat::Internal),
        (&["turtle"], ResultQueryFormat::Turtle),
        (&["ntriples"], ResultQueryFormat::NTriples),
        (&["json-ld", "jsonld"], ResultQueryFormat::JsonLd),
        (&["json"], ResultQueryFormat::Json),
        (&["rdf-xml", "rdfxml"], ResultQueryFormat::RdfXml),
        (&["csv"], ResultQueryFormat::Csv),
        (&["markdown"], ResultQueryFormat::Markdown),
        (&["trig"], ResultQueryFormat::TriG),
        (&["n3"], ResultQueryFormat::N3),
        (&["nquads"], ResultQueryFormat::NQuads),
    ];
}

impl FromStr for ResultQueryFormat {
    type Err = QueryError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::FORMATS
            .iter()
            .find(|(aliases, _)| aliases.iter().any(|a| a.eq_ignore_ascii_case(s)))
            .map(|(_, format)| *format)
            .ok_or_else(|| QueryError::UnsupportedResultQueryFormat {
                format: s.to_string(),
                formats: Self::FORMATS
                    .iter()
                    .flat_map(|(aliases, _)| aliases.iter())
                    .map(|a| a.to_string())
                    .collect(),
            })
    }
}
// ============================================================================
// QueryType
// ============================================================================

impl Display for QueryType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            QueryType::Select => "select",
            QueryType::Construct => "construct",
            QueryType::Ask => "ask",
            QueryType::Describe => "describe",
        };
        write!(f, "{s}")
    }
}

impl QueryType {
    const VARIANTS: &'static [(&'static str, QueryType)] = &[
        ("select", QueryType::Select),
        ("construct", QueryType::Construct),
        ("ask", QueryType::Ask),
        ("describe", QueryType::Describe),
    ];
}

impl FromStr for QueryType {
    type Err = QueryError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        QueryType::VARIANTS
            .iter()
            .find(|(name, _)| name.eq_ignore_ascii_case(s))
            .map(|(_, variant)| *variant)
            .ok_or_else(|| QueryError::UnsupportedQueryType {
                query_type: s.to_string(),
                variants: QueryType::VARIANTS.iter().map(|(name, _)| name.to_string()).collect(),
            })
    }
}
