use crate::sql::expression::identifier::SimpleIdentifier;
use crate::sql::expression::{OrderByExpression, SQLExpression};
use crate::write_utils::pad;
use derive_more::From;
use ordermap::OrderMap;
use std::fmt;
use std::fmt::{Display, Formatter};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Windows {
    pub windows: OrderMap<SimpleIdentifier, WindowSpecification>,
}

impl Windows {
    pub fn with(mut self, identifier: SimpleIdentifier, window: WindowSpecification) -> Self {
        self.windows.insert(identifier, window);
        self
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> fmt::Result {
        if self.windows.is_empty() {
            Ok(())
        } else {
            writeln!(f)?;
            pad(f, indentation)?;
            write!(f, "WINDOW")?;
            for (i, (si, window_spec)) in self.windows.iter().enumerate() {
                if i != 0 {
                    write!(f, ",")?;
                }
                write!(f, " {} AS ", si.to_string())?;
                window_spec.fmt_indented(f, indentation + 4)?;
            }
            Ok(())
        }
    }
}

impl Display for Windows {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, 0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct WindowSpecification {
    pub partition_by: Vec<SQLExpression>,
    pub order_by: Vec<OrderByExpression>,
    pub frame: Option<WindowFrame>,
}

impl WindowSpecification {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> fmt::Result {
        write!(f, "(")?;
        let spec_indentation = indentation + 4;
        if !self.partition_by.is_empty() {
            writeln!(f)?;
            pad(f, spec_indentation)?;
            write!(f, "PARTITION BY ")?;
            let partition_count = self.partition_by.len();
            for (i, expr) in self.partition_by.iter().enumerate() {
                write!(f, "{}", expr)?;
                if i != partition_count - 1 {
                    write!(f, ", ")?;
                }
            }
        };

        if !self.order_by.is_empty() {
            writeln!(f)?;
            pad(f, spec_indentation)?;
            write!(f, "ORDER BY ")?;
            let order_count = self.order_by.len();
            for (i, expr) in self.order_by.iter().enumerate() {
                write!(f, "{}", expr)?;
                if i != order_count - 1 {
                    write!(f, ", ")?;
                }
            }
        };
        if let Some(frame) = &self.frame {
            writeln!(f)?;
            frame.fmt_indented(f, spec_indentation)?;
        }
        writeln!(f)?;
        pad(f, indentation)?;
        write!(f, ")")
    }

    fn subelements(&self) -> usize {
        self.partition_by
            .iter()
            .map(|e| e.subelements() + 1)
            .sum::<usize>()
            + self
                .order_by
                .iter()
                .map(|e| e.expression.subelements() + 1)
                .sum::<usize>()
            + 1 // For the frame
    }
}

impl Display for WindowSpecification {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, 0)
    }
}

#[derive(Debug, Clone)]
pub enum Direction {
    Asc,
    Desc,
}

impl Direction {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> fmt::Result {
        pad(f, indentation)?;
        match self {
            Direction::Asc => write!(f, "ASC"),
            Direction::Desc => write!(f, "DESC"),
        }
    }
}
impl Display for Direction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, 0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WindowFrame {
    pub frame_type: FrameType,
    pub preceding: FrameBoundary,
    pub following: FrameBoundary,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameType {
    RANGE,
    ROWS,
}

impl FrameType {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> fmt::Result {
        pad(f, indentation)?;
        match self {
            FrameType::RANGE => write!(f, "RANGE"),
            FrameType::ROWS => write!(f, "ROWS"),
        }
    }
}

impl Display for FrameType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, 0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameBoundary {
    Unbounded,
    CurrentRowBoundary,
    BoundaryExpression(Box<SQLExpression>),
}

impl WindowFrame {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> fmt::Result {
        self.frame_type.fmt_indented(f, indentation)?;
        write!(f, " BETWEEN ")?;
        self.render_frame_boundary(f, &self.preceding, "PRECEDING")?;
        write!(f, " AND ")?;
        self.render_frame_boundary(f, &self.following, "FOLLOWING")
    }

    fn render_frame_boundary(
        &self,
        f: &mut Formatter<'_>,
        boundary: &FrameBoundary,
        position: &str,
    ) -> fmt::Result {
        match boundary {
            FrameBoundary::Unbounded => write!(f, "UNBOUNDED {}", position),
            FrameBoundary::CurrentRowBoundary => write!(f, "CURRENT ROW"),
            FrameBoundary::BoundaryExpression(expr) => {
                write!(f, "{} {}", expr.to_string(), position)
            }
        }
    }
}

impl Display for WindowFrame {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, 0)
    }
}

#[derive(Debug, Clone, From, PartialEq, Eq)]
pub enum WindowReference {
    NamedWindowReference(NamedWindowReference),
    WindowSpecification(WindowSpecification),
}

impl WindowReference {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> fmt::Result {
        match self {
            WindowReference::NamedWindowReference(l) => l.fmt_indented(f, indentation),
            WindowReference::WindowSpecification(l) => l.fmt_indented(f, indentation),
        }
    }

    pub fn subelements(&self) -> usize {
        match self {
            WindowReference::NamedWindowReference(_) => 0,
            WindowReference::WindowSpecification(l) => l.subelements(),
        }
    }
}

impl Display for WindowReference {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, 0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamedWindowReference {
    pub name: SimpleIdentifier,
}

impl NamedWindowReference {
    pub fn new(name: SimpleIdentifier) -> Self {
        Self { name }
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> fmt::Result {
        self.name.fmt_indented(f, indentation)
    }
}

impl Display for NamedWindowReference {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, 0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WindowExpression {
    pub expression: Box<SQLExpression>,
    pub window_reference: WindowReference,
}

impl WindowExpression {
    pub fn new(expression: SQLExpression, window_reference: WindowReference) -> Self {
        Self {
            expression: Box::new(expression),
            window_reference,
        }
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> fmt::Result {
        self.expression.fmt_indented(f, indentation)?;
        write!(f, " OVER ")?;
        self.window_reference.fmt_indented(f, indentation)
    }

    pub(crate) fn subelements(&self) -> usize {
        self.expression.subelements() + self.window_reference.subelements() + 1
    }
}

impl Display for WindowExpression {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, 0)
    }
}
