use core::{
    ffi::{c_char, c_int, c_long},
    ptr::null_mut,
};

use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike, Utc};

use super::{
    sys_time::__ygg_timespec_t,
    sys_types::{clock_t, clockid_t, time_t},
};

mod convert;
mod string;
mod timer;
mod util;

#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
#[repr(C)]
pub struct itimerspec {
    pub it_interval: __ygg_timespec_t,
    pub it_value: __ygg_timespec_t,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
#[repr(C)]
pub struct tm {
    pub tm_sec: c_int,
    pub tm_min: c_int,
    pub tm_hour: c_int,
    pub tm_mday: c_int,
    pub tm_mon: c_int,
    pub tm_year: c_int,
    pub tm_wday: c_int,
    pub tm_yday: c_int,
    pub tm_isdst: c_int,
}

pub type __ygg_tm_t = tm;

pub const CLOCKS_PER_SEC: clock_t = 1000000;

pub const CLOCK_MONOTONIC: clockid_t = 1;
pub const CLOCK_PROCESS_CPUTIME_ID: clockid_t = 2;
pub const CLOCK_REALTIME: clockid_t = 3;
pub const CLOCK_THREAD_CPUTIME_ID: clockid_t = 4;

pub const TIMER_ABSTIME: c_int = 1 << 0;

#[no_mangle]
pub static mut daylight: c_int = 0;
#[no_mangle]
pub static mut timezone: c_long = 0;
#[no_mangle]
pub static mut tzname: *mut *mut c_char = null_mut();

pub trait CDateTime {
    fn to_utc_datetime(&self) -> Option<DateTime<Utc>>;
    fn to_datetime<Tz: TimeZone>(&self, tz: Tz) -> Option<DateTime<Tz>>;
}

impl tm {
    pub fn naive_date(&self) -> Option<NaiveDate> {
        NaiveDate::from_ymd_opt(
            self.tm_year + 1900,
            u32::try_from(self.tm_mon).ok()? + 1,
            u32::try_from(self.tm_mday).ok()?,
        )
    }

    pub fn naive_time(&self) -> Option<NaiveTime> {
        NaiveTime::from_hms_opt(
            u32::try_from(self.tm_hour).ok()?,
            u32::try_from(self.tm_min).ok()?,
            u32::try_from(self.tm_sec).ok()?,
        )
    }
}

impl CDateTime for tm {
    fn to_utc_datetime(&self) -> Option<DateTime<Utc>> {
        let dt = NaiveDateTime::new(self.naive_date()?, self.naive_time()?);
        Some(dt.and_utc())
    }

    fn to_datetime<Tz: TimeZone>(&self, tz: Tz) -> Option<DateTime<Tz>> {
        let dt = NaiveDateTime::new(self.naive_date()?, self.naive_time()?);
        dt.and_local_timezone(tz).latest()
    }
}

impl CDateTime for time_t {
    fn to_utc_datetime(&self) -> Option<DateTime<Utc>> {
        DateTime::from_timestamp(self.0, 0)
    }

    fn to_datetime<Tz: TimeZone>(&self, tz: Tz) -> Option<DateTime<Tz>> {
        tz.timestamp_opt(self.0, 0).latest()
    }
}

impl<T: Datelike + Timelike> From<T> for tm {
    fn from(value: T) -> Self {
        Self {
            tm_sec: value.second() as _,
            tm_min: value.minute() as _,
            tm_hour: value.hour() as _,
            tm_mday: value.day() as _,
            tm_mon: value.month0() as _,
            tm_year: value.year() - 1900,
            tm_wday: value.weekday().num_days_from_sunday() as _,
            tm_yday: value.ordinal0() as _,
            tm_isdst: 0,
        }
    }
}

pub unsafe fn get_timezone() -> impl TimeZone {
    // TODO get it from C
    Utc
}
