use candid::{CandidType, Deserialize};
use ex3_canister_constant::{
    PAGE_INPUT_MAX_LIMIT, PAGE_INPUT_MAX_OFFSET, PAGE_INPUT_MIN_LIMIT, PAGE_INPUT_MIN_OFFSET,
};

use crate::error::PagerError;
use crate::result::ServiceResult;

#[derive(CandidType, Deserialize, Debug)]
pub struct GetPageInput {
    pub offset: usize,
    pub limit: usize,
}

impl GetPageInput {
    pub fn validate(&self) -> ServiceResult<()> {
        let max_offset = PAGE_INPUT_MAX_OFFSET;
        let min_offset = PAGE_INPUT_MIN_OFFSET;
        if self.offset > max_offset || self.offset < min_offset {
            return Err(PagerError::ValueShouldBeInRangeError {
                field: "offset".to_string(),
                min: min_offset,
                max: max_offset,
            }
            .into());
        }
        let max_limit = PAGE_INPUT_MAX_LIMIT;
        let min_limit = PAGE_INPUT_MIN_LIMIT;
        if self.limit > max_limit || self.limit < min_limit {
            return Err(PagerError::ValueShouldBeInRangeError {
                field: "limit".to_string(),
                min: min_limit,
                max: max_limit,
            }
            .into());
        }
        Ok(())
    }
}

#[derive(CandidType, Deserialize, Debug)]
pub struct GetPageOutput<T> {
    pub items: Vec<T>,
}

impl<T> GetPageOutput<T> {
    pub fn new(items: Vec<T>) -> Self {
        Self { items }
    }
}

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

    use ex3_canister_test_logger::init_test;

    #[fixture]
    pub fn setup() {
        init_test();
    }

    mod get_page_input {
        use ex3_canister_constant::{PAGE_INPUT_MAX_LIMIT, PAGE_INPUT_MAX_OFFSET};

        use crate::error::PagerError;
        use crate::pager::GetPageInput;

        use super::*;

        #[rstest]
        fn test_get_page_input(_setup: ()) {
            let input = GetPageInput {
                limit: 10,
                offset: 0,
            };
            assert_eq!(input.validate(), Ok(()));
        }

        #[rstest]
        fn test_get_page_input_limit_overflow(_setup: ()) {
            let input = GetPageInput {
                limit: PAGE_INPUT_MAX_LIMIT + 1,
                offset: 0,
            };
            assert_eq!(
                input.validate(),
                Err(PagerError::ValueShouldBeInRangeError {
                    field: "limit".to_string(),
                    min: 1,
                    max: PAGE_INPUT_MAX_LIMIT,
                }
                .into())
            );
        }

        #[rstest]
        fn test_get_page_input_offset_overflow(_setup: ()) {
            let input = GetPageInput {
                limit: 100,
                offset: PAGE_INPUT_MAX_OFFSET + 1,
            };
            assert_eq!(
                input.validate(),
                Err(PagerError::ValueShouldBeInRangeError {
                    field: "offset".to_string(),
                    min: 0,
                    max: PAGE_INPUT_MAX_OFFSET,
                }
                .into())
            );
        }
    }
}
