/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/

use crate::{Filter, MultiBehaviour, Query, Sort, SortOrder, ValueSort};
use anyhow::{Error, anyhow, bail};
use jellyobject::{Path, Value};
use std::{fmt::Display, str::FromStr};

impl Display for Query<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if !matches!(self.filter, Filter::True) {
            write!(f, "FILTER {} ", self.filter)?;
        }
        if !matches!(self.sort, Sort::None) {
            write!(f, "SORT {} ", self.sort)?;
        }
        Ok(())
    }
}
impl Display for Filter<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Filter::True => write!(f, "TRUE"),
            Filter::All(filters) => write!(
                f,
                "({})",
                filters
                    .iter()
                    .map(|f| f.to_string())
                    .collect::<Vec<_>>()
                    .join(" AND ")
            ),
            Filter::Any(filters) => write!(
                f,
                "({})",
                filters
                    .iter()
                    .map(|f| f.to_string())
                    .collect::<Vec<_>>()
                    .join(" OR ")
            ),
            Filter::Match(path, value) => {
                write!(f, "{path} = {value}")
            }
            Filter::Has(path) => write!(f, "{path}"),
        }
    }
}

impl Display for Sort {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Sort::None => write!(f, "NONE"),
            Sort::Random(0) => write!(f, "RANDOM"),
            Sort::Random(seed) => write!(f, "RANDOM WITH SEED {seed}"),
            Sort::Value(ValueSort {
                multi, order, path, ..
            }) => {
                write!(
                    f,
                    "{} BY {} {path}",
                    match order {
                        SortOrder::Ascending => "ASCENDING",
                        SortOrder::Descending => "DESCENDING",
                    },
                    match multi {
                        MultiBehaviour::Count => "COUNT",
                        MultiBehaviour::First => "FIRST",
                        MultiBehaviour::ForEach => "EACH",
                        MultiBehaviour::Max => "MAX",
                        MultiBehaviour::Min => "MIN",
                    },
                )
            }
            Sort::TextSearch(path, value) => {
                write!(f, "TEXT SEARCH {path} = {value:?}")
            }
        }
    }
}

impl FromStr for Query<'static> {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some((filter, sort)) = s.split_once(" SORT ")
            && let Some(filter) = filter.strip_prefix("FILTER ")
        {
            Ok(Self {
                filter: Filter::from_str(filter)?,
                sort: Sort::from_str(sort)?,
                ..Default::default()
            })
        } else if let Some(sort) = s.strip_prefix("SORT ") {
            Ok(Self {
                sort: Sort::from_str(sort)?,
                ..Default::default()
            })
        } else if let Some(filter) = s.strip_prefix("FILTER") {
            Ok(Self {
                filter: Filter::from_str(filter)?,
                ..Default::default()
            })
        } else {
            bail!("invalid query")
        }
    }
}
impl FromStr for Filter<'static> {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "TRUE" => Self::True,
            x if let Some(x) = x.strip_prefix("(")
                && let Some(x) = x.strip_suffix(")")
                && let Some((l, r)) = x.split_once("AND") =>
            {
                Self::All(vec![Filter::from_str(l)?, Filter::from_str(r)?])
            }
            x if let Some(x) = x.strip_prefix("(")
                && let Some(x) = x.strip_suffix(")")
                && let Some((l, r)) = x.split_once("OR") =>
            {
                Self::Any(vec![
                    Filter::from_str(l.trim())?,
                    Filter::from_str(r.trim())?,
                ])
            }
            x if let Some((l, r)) = x.split_once("=") => Self::Match(
                Path::from_str(l.trim()).map_err(|e| anyhow!("{e}"))?,
                Value::from_str(r.trim()).map_err(|e| anyhow!("{e}"))?,
            ),
            x => Self::Has(Path::from_str(x.trim()).map_err(|e| anyhow!("{e}"))?),
        })
    }
}
impl FromStr for Sort {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(if s == "NONE" {
            Sort::None
        } else if s == "RANDOM" {
            Sort::Random(1)
        } else if let Some(s) = s.strip_prefix("TEXT SEARCH ")
            && let Some((path, value)) = s.split_once(" = ")
        {
            Sort::TextSearch(
                Path::from_str(path).map_err(|e| anyhow!("{e}"))?,
                value.to_owned(),
            )
        } else if let Some((order, rest)) = s.split_once(" BY ")
            && let Some((multi, path)) = rest.split_once(" ")
        {
            Sort::Value(ValueSort {
                order: match order {
                    "ASCENDING" => SortOrder::Ascending,
                    "DESCENDING" => SortOrder::Descending,
                    _ => bail!("unknown order"),
                },
                multi: match multi {
                    "COUNT" => MultiBehaviour::Count,
                    "FIRST" => MultiBehaviour::First,
                    "EACH" => MultiBehaviour::ForEach,
                    "MAX" => MultiBehaviour::Max,
                    "MIN" => MultiBehaviour::Min,
                    _ => bail!("unknown multi bahav"),
                },
                path: Path::from_str(path).map_err(|e| anyhow!("{e}"))?,
                offset: None,
            })
        } else {
            bail!("unknown sort")
        })
    }
}

#[test]
fn test_parse() {
    use jellyobject::Tag;
    assert_eq!(
        Query::from_str("FILTER (visi = visi AND kind = vide) SORT DESCENDING BY FIRST rldt")
            .unwrap(),
        Query {
            filter: Filter::All(vec![
                Filter::Match(Path(vec![Tag::new(b"visi")]), Tag::new(b"visi").into()),
                Filter::Match(Path(vec![Tag::new(b"kind")]), Tag::new(b"vide").into()),
            ]),
            sort: Sort::Value(ValueSort {
                order: SortOrder::Descending,
                path: Path(vec![Tag::new(b"rldt")]),
                multi: MultiBehaviour::First,
                offset: None,
            }),
            ..Default::default()
        }
    )
}
