use super::error::AppError;
use nanoid::nanoid;
use serde::{Deserialize, Serialize};
use sqlx::types::time::OffsetDateTime;
use std::{
    str::FromStr,
    time::{SystemTime, UNIX_EPOCH},
};
use time::format_description::well_known::Iso8601;

const ALPHANUMERIC_ALPHABET: [char; 62] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
    'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
    'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
    'V', 'W', 'X', 'Y', 'Z',
];

pub fn nano_id() -> String {
    nano_id_of(21)
}

pub fn nano_id_of(size: usize) -> String {
    nanoid!(size)
        .chars()
        .map(|c| {
            if c.is_ascii_alphanumeric() {
                c
            } else {
                nanoid!(1, &ALPHANUMERIC_ALPHABET).chars().next().unwrap()
            }
        })
        .collect()
}

#[allow(dead_code)]
pub trait ToOffsetDateTime {
    fn _to_offset_date_time_from_epoch(&self) -> Result<OffsetDateTime, AppError>;
    fn _to_offset_date_time_from_iso(&self) -> Result<OffsetDateTime, AppError>;
}

impl ToOffsetDateTime for String {
    fn _to_offset_date_time_from_epoch(&self) -> Result<OffsetDateTime, AppError> {
        let timestamp = i64::from_str(self).map_err(|_| AppError::UnprocessableEntity {
            error_values: None,
            message: Some("Invalid timestamp".into()),
            description: None,
        })?;

        OffsetDateTime::from_unix_timestamp(timestamp).map_err(|_| AppError::UnprocessableEntity {
            error_values: None,
            message: Some("Invalid timestamp".into()),
            description: None,
        })
    }

    fn _to_offset_date_time_from_iso(&self) -> Result<OffsetDateTime, AppError> {
        OffsetDateTime::parse(self, &Iso8601::DEFAULT).map_err(|_| AppError::UnprocessableEntity {
            error_values: None,
            message: Some("Invalid timestamp".into()),
            description: None,
        })
    }
}

pub trait FromOffsetDateTime {
    fn _to_unix_string(&self) -> Result<String, AppError>;
    fn to_iso_string(&self) -> Result<String, AppError>;
}

impl FromOffsetDateTime for OffsetDateTime {
    fn _to_unix_string(&self) -> Result<String, AppError> {
        Ok(self.unix_timestamp().to_string())
    }

    fn to_iso_string(&self) -> Result<String, AppError> {
        self.format(&Iso8601::DEFAULT)
            .map(|s| s.to_string())
            .map_err(|_| AppError::UnprocessableEntity {
                error_values: None,
                message: Some("Invalid timestamp".into()),
                description: None,
            })
    }
}

pub fn validate_timestamp(timestamp: i64, is_in_past: bool) -> Result<bool, AppError> {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| {
            AppError::internal_error(
                e.to_string(),
                Some("Failed to get current time".to_string()),
            )
        })?
        .as_secs() as i64;

    if is_in_past {
        Ok(timestamp < now)
    } else {
        Ok(timestamp > now)
    }
}

#[allow(dead_code)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PaginationParams {
    pub page_number: Option<i32>,
    pub page_size: Option<i32>,
}
