use std::cmp;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SortDirection {
    Asc,
    Desc,
}

impl SortDirection {
    pub const fn as_str(self) -> &'static str {
        match self {
            SortDirection::Asc => "asc",
            SortDirection::Desc => "desc",
        }
    }

    pub const fn opposite(self) -> Self {
        match self {
            SortDirection::Asc => SortDirection::Desc,
            SortDirection::Desc => SortDirection::Asc,
        }
    }
}

pub trait SortKey: Copy + Eq {
    fn default() -> Self;
    fn from_query(value: &str) -> Option<Self>;
    fn query_value(self) -> &'static str;
    fn default_direction(self) -> SortDirection;
}

/// Generates a sort key enum and its `SortKey` trait implementation.
///
/// # Example
///
/// ```ignore
/// define_sort_key!(RoasterSortKey {
///     #[default]
///     CreatedAt("created-at", Desc),
///     Name("name", Asc),
///     Country("country", Asc),
///     City("city", Asc),
/// });
/// ```
#[macro_export]
macro_rules! define_sort_key {
    (
        $vis:vis $name:ident {
            $(#[default] $default_variant:ident($default_query:literal, $default_dir:ident),)?
            $($variant:ident($query:literal, $dir:ident)),*
            $(,)?
        }
    ) => {
        #[derive(Debug, Clone, Copy, Eq, PartialEq)]
        $vis enum $name {
            $($default_variant,)?
            $($variant),*
        }

        impl $crate::domain::listing::SortKey for $name {
            fn default() -> Self {
                // The first arm expands for the #[default] variant
                $( return $name::$default_variant; )?
                // If no #[default], this won't compile — every invocation needs one
            }

            fn from_query(value: &str) -> Option<Self> {
                match value {
                    $($default_query => Some($name::$default_variant),)?
                    $($query => Some($name::$variant),)*
                    _ => None,
                }
            }

            fn query_value(self) -> &'static str {
                match self {
                    $($name::$default_variant => $default_query,)?
                    $($name::$variant => $query,)*
                }
            }

            fn default_direction(self) -> $crate::domain::listing::SortDirection {
                match self {
                    $($name::$default_variant => $crate::domain::listing::SortDirection::$default_dir,)?
                    $($name::$variant => $crate::domain::listing::SortDirection::$dir,)*
                }
            }
        }
    };
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PageSize {
    Limited(u32),
    All,
}

impl PageSize {
    pub fn limited(size: u32) -> Self {
        if size == 0 {
            PageSize::All
        } else {
            PageSize::Limited(size)
        }
    }

    pub const fn is_all(self) -> bool {
        matches!(self, PageSize::All)
    }

    pub const fn as_option(self) -> Option<u32> {
        match self {
            PageSize::Limited(value) => Some(value),
            PageSize::All => None,
        }
    }

    pub fn to_query_value(self) -> String {
        match self {
            PageSize::All => "all".to_string(),
            PageSize::Limited(value) => value.to_string(),
        }
    }
}

pub const DEFAULT_PAGE_SIZE: u32 = 10;
pub const MAX_PAGE_SIZE: u32 = 50;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct ListRequest<K: SortKey> {
    pub page: u32,
    pub page_size: PageSize,
    pub sort_key: K,
    pub sort_direction: SortDirection,
}

impl<K: SortKey> ListRequest<K> {
    pub fn new(page: u32, page_size: PageSize, sort_key: K, sort_direction: SortDirection) -> Self {
        let page = page.max(1);
        let page_size = match page_size {
            PageSize::Limited(size) => {
                if size == 0 {
                    PageSize::All
                } else {
                    let clamped = cmp::min(size, MAX_PAGE_SIZE).max(1);
                    PageSize::Limited(clamped)
                }
            }
            PageSize::All => PageSize::All,
        };

        Self {
            page,
            page_size,
            sort_key,
            sort_direction,
        }
    }

    pub fn default_query() -> Self {
        let key = K::default();
        Self::new(
            1,
            PageSize::Limited(DEFAULT_PAGE_SIZE),
            key,
            key.default_direction(),
        )
    }

    pub fn show_all(sort_key: K, sort_direction: SortDirection) -> Self {
        Self::new(1, PageSize::All, sort_key, sort_direction)
    }

    pub const fn page(&self) -> u32 {
        self.page
    }

    pub const fn page_size(&self) -> PageSize {
        self.page_size
    }

    pub const fn sort_key(&self) -> K {
        self.sort_key
    }

    pub const fn sort_direction(&self) -> SortDirection {
        self.sort_direction
    }

    pub fn with_page(self, page: u32) -> Self {
        Self {
            page: page.max(1),
            ..self
        }
    }

    pub fn with_page_size(self, page_size: PageSize) -> Self {
        Self::new(self.page, page_size, self.sort_key, self.sort_direction)
    }

    pub fn with_sort(self, key: K) -> Self {
        let direction = if key == self.sort_key {
            self.sort_direction.opposite()
        } else {
            key.default_direction()
        };
        Self::new(self.page, self.page_size, key, direction)
    }

    pub fn with_sort_and_direction(self, key: K, direction: SortDirection) -> Self {
        Self::new(self.page, self.page_size, key, direction)
    }

    pub fn ensure_page_within(self, total: u64) -> Self {
        if matches!(self.page_size, PageSize::All) {
            return Self::new(1, PageSize::All, self.sort_key, self.sort_direction);
        }

        let Some(limit) = self.page_size.as_option() else {
            return self;
        };

        if total == 0 {
            return Self::new(1, self.page_size, self.sort_key, self.sort_direction);
        }

        let last_page = (total.div_ceil(u64::from(limit))) as u32;
        let adjusted_page = self.page.min(last_page.max(1));
        Self::new(
            adjusted_page,
            self.page_size,
            self.sort_key,
            self.sort_direction,
        )
    }
}

#[derive(Debug, Clone)]
pub struct Page<T> {
    pub items: Vec<T>,
    pub page: u32,
    pub page_size: u32,
    pub total: u64,
    pub showing_all: bool,
}

impl<T> Page<T> {
    pub fn new(items: Vec<T>, page: u32, page_size: u32, total: u64, showing_all: bool) -> Self {
        Self {
            items,
            page: page.max(1),
            page_size: page_size.max(1),
            total,
            showing_all,
        }
    }

    pub fn total_pages(&self) -> u32 {
        if self.total == 0 || self.showing_all {
            1
        } else {
            let size = u64::from(self.page_size);
            (self.total.div_ceil(size)) as u32
        }
    }

    pub fn has_previous(&self) -> bool {
        !self.showing_all && self.page > 1
    }

    pub fn has_next(&self) -> bool {
        !self.showing_all && self.page < self.total_pages()
    }

    pub fn start_index(&self) -> u64 {
        if self.total == 0 {
            0
        } else {
            u64::from(self.page - 1) * u64::from(self.page_size) + 1
        }
    }

    pub fn end_index(&self) -> u64 {
        if self.total == 0 {
            0
        } else {
            self.start_index() + self.items.len() as u64 - 1
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    crate::define_sort_key!(TestSortKey {
        #[default]
        CreatedAt("created-at", Desc),
        Name("name", Asc),
    });

    // --- PageSize tests ---

    #[test]
    fn zero_becomes_all() {
        assert_eq!(PageSize::limited(0), PageSize::All);
    }

    #[test]
    fn limited_returns_value() {
        assert_eq!(PageSize::limited(10).as_option(), Some(10));
    }

    #[test]
    fn all_has_no_option() {
        assert_eq!(PageSize::All.as_option(), None);
    }

    #[test]
    fn all_is_all() {
        assert!(PageSize::All.is_all());
    }

    // --- ListRequest::new tests ---

    #[test]
    fn page_min_is_1() {
        let req = ListRequest::new(
            0,
            PageSize::Limited(10),
            TestSortKey::CreatedAt,
            SortDirection::Desc,
        );
        assert_eq!(req.page, 1);
    }

    #[test]
    fn size_clamped_to_max() {
        let req = ListRequest::new(
            1,
            PageSize::Limited(51),
            TestSortKey::CreatedAt,
            SortDirection::Desc,
        );
        assert_eq!(req.page_size, PageSize::Limited(MAX_PAGE_SIZE));
    }

    #[test]
    fn zero_size_becomes_all() {
        let req = ListRequest::new(
            1,
            PageSize::Limited(0),
            TestSortKey::CreatedAt,
            SortDirection::Desc,
        );
        assert_eq!(req.page_size, PageSize::All);
    }

    // --- ListRequest::default_query tests ---

    #[test]
    fn default_query_has_page_1() {
        let req = ListRequest::<TestSortKey>::default_query();
        assert_eq!(req.page, 1);
    }

    #[test]
    fn default_query_has_default_page_size() {
        let req = ListRequest::<TestSortKey>::default_query();
        assert_eq!(req.page_size, PageSize::Limited(DEFAULT_PAGE_SIZE));
    }

    #[test]
    fn default_query_has_default_sort_key() {
        let req = ListRequest::<TestSortKey>::default_query();
        assert_eq!(req.sort_key, TestSortKey::CreatedAt);
    }

    #[test]
    fn default_query_has_default_sort_direction() {
        let req = ListRequest::<TestSortKey>::default_query();
        assert_eq!(req.sort_direction, SortDirection::Desc);
    }

    // --- with_sort tests ---

    #[test]
    fn with_sort_same_key_toggles_direction() {
        let req = ListRequest::new(
            1,
            PageSize::Limited(10),
            TestSortKey::CreatedAt,
            SortDirection::Desc,
        );
        let toggled = req.with_sort(TestSortKey::CreatedAt);
        assert_eq!(toggled.sort_direction, SortDirection::Asc);
    }

    #[test]
    fn with_sort_new_key_uses_default_direction() {
        let req = ListRequest::new(
            1,
            PageSize::Limited(10),
            TestSortKey::CreatedAt,
            SortDirection::Desc,
        );
        let switched = req.with_sort(TestSortKey::Name);
        assert_eq!(switched.sort_direction, SortDirection::Asc);
    }

    // --- ensure_page_within tests ---

    #[test]
    fn clamps_page_beyond_total() {
        let req = ListRequest::new(
            10,
            PageSize::Limited(10),
            TestSortKey::CreatedAt,
            SortDirection::Desc,
        );
        let clamped = req.ensure_page_within(25);
        assert_eq!(clamped.page, 3);
    }

    #[test]
    fn zero_total_returns_page_1() {
        let req = ListRequest::new(
            5,
            PageSize::Limited(10),
            TestSortKey::CreatedAt,
            SortDirection::Desc,
        );
        let clamped = req.ensure_page_within(0);
        assert_eq!(clamped.page, 1);
    }

    #[test]
    fn all_mode_returns_page_1() {
        let req = ListRequest::new(
            3,
            PageSize::All,
            TestSortKey::CreatedAt,
            SortDirection::Desc,
        );
        let clamped = req.ensure_page_within(100);
        assert_eq!(clamped.page, 1);
    }

    // --- Page tests ---

    #[test]
    fn total_pages_calculation() {
        let page: Page<()> = Page::new(vec![], 1, 10, 25, false);
        assert_eq!(page.total_pages(), 3);
    }

    #[test]
    fn total_pages_zero_is_1() {
        let page: Page<()> = Page::new(vec![], 1, 10, 0, false);
        assert_eq!(page.total_pages(), 1);
    }

    #[test]
    fn has_previous_false_on_page_1() {
        let page: Page<()> = Page::new(vec![], 1, 10, 30, false);
        assert!(!page.has_previous());
    }

    #[test]
    fn has_next_false_on_last_page() {
        let page: Page<()> = Page::new(vec![], 3, 10, 30, false);
        assert!(!page.has_next());
    }

    #[test]
    fn start_end_index() {
        let page: Page<i32> = Page::new(vec![1, 2, 3], 2, 10, 30, false);
        assert_eq!(page.start_index(), 11);
        assert_eq!(page.end_index(), 13);
    }

    #[test]
    fn start_end_index_zero_total() {
        let page: Page<()> = Page::new(vec![], 1, 10, 0, false);
        assert_eq!(page.start_index(), 0);
        assert_eq!(page.end_index(), 0);
    }

    #[test]
    fn showing_all_single_page() {
        let page: Page<i32> = Page::new(vec![1, 2, 3], 1, 3, 3, true);
        assert_eq!(page.total_pages(), 1);
        assert!(!page.has_previous());
        assert!(!page.has_next());
    }
}
