use cap_std::time::{Duration, Instant, SystemClock, SystemTime};
use cap_std::{AmbientAuthority, ambient_authority};
use cap_time_ext::{MonotonicClockExt as _, SystemClockExt as _};
use std::error::Error;
use std::fmt;
use wasmtime::component::{HasData, ResourceTable};

/// A helper struct which implements [`HasData`] for the `wasi:clocks` APIs.
///
/// This can be useful when directly calling `add_to_linker` functions directly,
/// such as [`wasmtime_wasi::p2::bindings::clocks::monotonic_clock::add_to_linker`] as
/// the `D` type parameter. See [`HasData`] for more information about the type
/// parameter's purpose.
///
/// When using this type you can skip the [`WasiClocksView`] trait, for
/// example.
///
/// [`wasmtime_wasi::p2::bindings::clocks::monotonic_clock::add_to_linker`]: crate::p2::bindings::clocks::monotonic_clock::add_to_linker
///
/// # Examples
///
/// ```
/// use wasmtime::component::{Linker, ResourceTable};
/// use wasmtime::{Engine, Result};
/// use wasmtime_wasi::clocks::*;
///
/// struct MyStoreState {
///     table: ResourceTable,
///     clocks: WasiClocksCtx,
/// }
///
/// fn main() -> Result<()> {
///     let engine = Engine::default();
///     let mut linker = Linker::new(&engine);
///
///     wasmtime_wasi::p2::bindings::clocks::monotonic_clock::add_to_linker::<MyStoreState, WasiClocks>(
///         &mut linker,
///         |state| WasiClocksCtxView {
///             table: &mut state.table,
///             ctx: &mut state.clocks,
///         },
///     )?;
///     Ok(())
/// }
/// ```
pub struct WasiClocks;

impl HasData for WasiClocks {
    type Data<'a> = WasiClocksCtxView<'a>;
}

pub struct WasiClocksCtx {
    pub(crate) wall_clock: Box<dyn HostWallClock + Send>,
    pub(crate) monotonic_clock: Box<dyn HostMonotonicClock + Send>,
}

impl Default for WasiClocksCtx {
    fn default() -> Self {
        Self {
            wall_clock: wall_clock(),
            monotonic_clock: monotonic_clock(),
        }
    }
}

pub trait WasiClocksView: Send {
    fn clocks(&mut self) -> WasiClocksCtxView<'_>;
}

pub struct WasiClocksCtxView<'a> {
    pub ctx: &'a mut WasiClocksCtx,
    pub table: &'a mut ResourceTable,
}

pub trait HostWallClock: Send {
    fn resolution(&self) -> Duration;
    fn now(&self) -> Duration;
}

pub trait HostMonotonicClock: Send {
    fn resolution(&self) -> u64;
    fn now(&self) -> u64;
}

pub struct WallClock {
    /// The underlying system clock.
    clock: cap_std::time::SystemClock,
}

impl Default for WallClock {
    fn default() -> Self {
        Self::new(ambient_authority())
    }
}

impl WallClock {
    pub fn new(ambient_authority: AmbientAuthority) -> Self {
        Self {
            clock: cap_std::time::SystemClock::new(ambient_authority),
        }
    }
}

impl HostWallClock for WallClock {
    fn resolution(&self) -> Duration {
        self.clock.resolution()
    }

    fn now(&self) -> Duration {
        // WASI defines wall clocks to return "Unix time".
        self.clock
            .now()
            .duration_since(SystemClock::UNIX_EPOCH)
            .unwrap()
    }
}

pub struct MonotonicClock {
    /// The underlying system clock.
    clock: cap_std::time::MonotonicClock,

    /// The `Instant` this clock was created. All returned times are
    /// durations since that time.
    initial: Instant,
}

impl Default for MonotonicClock {
    fn default() -> Self {
        Self::new(ambient_authority())
    }
}

impl MonotonicClock {
    pub fn new(ambient_authority: AmbientAuthority) -> Self {
        let clock = cap_std::time::MonotonicClock::new(ambient_authority);
        let initial = clock.now();
        Self { clock, initial }
    }
}

impl HostMonotonicClock for MonotonicClock {
    fn resolution(&self) -> u64 {
        self.clock.resolution().as_nanos().try_into().unwrap()
    }

    fn now(&self) -> u64 {
        // Unwrap here and in `resolution` above; a `u64` is wide enough to
        // hold over 584 years of nanoseconds.
        self.clock
            .now()
            .duration_since(self.initial)
            .as_nanos()
            .try_into()
            .unwrap()
    }
}

pub fn monotonic_clock() -> Box<dyn HostMonotonicClock + Send> {
    Box::new(MonotonicClock::default())
}

pub fn wall_clock() -> Box<dyn HostWallClock + Send> {
    Box::new(WallClock::default())
}

pub(crate) struct Datetime {
    pub seconds: i64,
    pub nanoseconds: u32,
}

impl TryFrom<SystemTime> for Datetime {
    type Error = DatetimeError;

    fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
        let epoch = SystemTime::from_std(std::time::SystemTime::UNIX_EPOCH);

        if time >= epoch {
            let duration = time.duration_since(epoch)?;
            Ok(Self {
                seconds: duration.as_secs().try_into()?,
                nanoseconds: duration.subsec_nanos(),
            })
        } else {
            let duration = epoch.duration_since(time)?;
            Ok(Self {
                seconds: -duration.as_secs().try_into()?,
                nanoseconds: duration.subsec_nanos(),
            })
        }
    }
}

#[derive(Debug)]
pub struct DatetimeError;

impl fmt::Display for DatetimeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("couldn't represent time as a WASI `Datetime`")
    }
}

impl Error for DatetimeError {}

impl From<std::time::SystemTimeError> for DatetimeError {
    fn from(_: std::time::SystemTimeError) -> Self {
        DatetimeError
    }
}

impl From<std::num::TryFromIntError> for DatetimeError {
    fn from(_: std::num::TryFromIntError) -> Self {
        DatetimeError
    }
}
