use neptunes::temporal::calendar::NEPTUNEDAYSECONDS;
use neptunes::temporal::calendar::*;
use neptunes::temporal::epoch::*;
use neptunes::temporal::time_scale::*;

// ── Calendar ─────────────────────────────────────────────────────────────

#[test]
fn datetime_new() {
    let dt = DateTime::new(2025, 1, 1, 0, 0, 0.0);
    assert_eq!(dt.year, 2025);
    assert_eq!(dt.month, 1);
    assert_eq!(dt.day, 1);
}

#[test]
fn datetime_to_julian_date() {
    let dt = DateTime::new(2000, 1, 1, 12, 0, 0.0);
    let jd = dt.tojuliandate();
    assert!((jd - 2451545.0).abs() < 1.0, "J2000 epoch");
}

#[test]
fn datetime_from_julian_date() {
    let dt = DateTime::fromjuliandate(2451545.0);
    assert_eq!(dt.year, 2000);
    assert_eq!(dt.month, 1);
}

#[test]
fn datetime_roundtrip_julian() {
    let dt1 = DateTime::new(2025, 6, 15, 12, 30, 0.0);
    let jd = dt1.tojuliandate();
    let dt2 = DateTime::fromjuliandate(jd);
    assert_eq!(dt1.year, dt2.year);
    assert_eq!(dt1.month, dt2.month);
    assert_eq!(dt1.day, dt2.day);
}

#[test]
fn datetime_to_unix_timestamp() {
    let dt = DateTime::new(1970, 1, 1, 0, 0, 0.0);
    let ts = dt.tounixtimestamp();
    assert!(ts.abs() < 86400.0, "Unix epoch at 0");
}

#[test]
fn datetime_from_unix_timestamp() {
    let dt = DateTime::fromunixtimestamp(0.0);
    assert_eq!(dt.year, 1970);
}

#[test]
fn datetime_to_neptune_day() {
    let dt = DateTime::new(2000, 1, 1, 12, 0, 0.0);
    let nd = dt.toneptuneday();
    assert!(nd.is_finite());
}

#[test]
fn datetime_to_neptune_year() {
    let dt = DateTime::new(2000, 1, 1, 12, 0, 0.0);
    let ny = dt.toneptuneyear();
    assert!(ny.is_finite());
}

#[test]
fn calendar_constants() {
    assert!((UNIXEPOCHJD - 2440587.5).abs() < 0.01);
    assert!((SECONDSPERDAY - 86400.0).abs() < 0.01);
    const { assert!(neptunes::temporal::calendar::NEPTUNEDAYSECONDS > 0.0) };
    const { assert!(neptunes::temporal::calendar::NEPTUNEYEARSECONDS > 0.0) };
}

// ── Epoch ────────────────────────────────────────────────────────────────

#[test]
fn epoch_j2000() {
    let ep = Epoch::j2000();
    assert!((ep.juliandate - 2451545.0).abs() < 1e-6);
}

#[test]
fn epoch_from_jd() {
    let ep = Epoch::fromjd(2451545.0);
    assert!((ep.juliandate - 2451545.0).abs() < 1e-6);
}

#[test]
fn epoch_from_mjd() {
    let mjd = 51544.5; // J2000 in MJD
    let ep = Epoch::frommjd(mjd);
    assert!((ep.juliandate - 2451545.0).abs() < 1e-3);
}

#[test]
fn epoch_to_mjd() {
    let ep = Epoch::j2000();
    let mjd = ep.tomjd();
    assert!((mjd - 51544.5).abs() < 1e-3);
}

#[test]
fn epoch_centuries_since_j2000_at_j2000() {
    let ep = Epoch::j2000();
    assert!(ep.centuriessincej2000().abs() < 1e-6);
}

#[test]
fn epoch_days_since_j2000_at_j2000() {
    let ep = Epoch::j2000();
    assert!(ep.dayssincej2000().abs() < 1e-6);
}

#[test]
fn epoch_neptune_gmst() {
    let ep = Epoch::j2000();
    let gmst = ep.neptunegmstdegrees();
    assert!(gmst.is_finite());
}

#[test]
fn epoch_advance_days() {
    let mut ep = Epoch::j2000();
    ep.advancedays(365.25);
    assert!((ep.juliandate - 2451545.0 - 365.25).abs() < 1e-6);
}

#[test]
fn epoch_advance_seconds() {
    let mut ep = Epoch::j2000();
    ep.advanceseconds(86400.0);
    assert!((ep.juliandate - 2451546.0).abs() < 1e-6);
}

#[test]
fn epoch_constants() {
    assert!((J2000EPOCH - 2451545.0).abs() < 1e-6);
    assert!((J1950EPOCH - 2433282.5).abs() < 1e-6);
    assert!((MJDOFFSET - 2400000.5).abs() < 1e-6);
}

// ── Time Scale ───────────────────────────────────────────────────────────

#[test]
fn timescale_realtime() {
    let ts = TimeScale::realtime();
    assert!((ts.speedmultiplier - 1.0).abs() < 1e-9);
    assert!(!ts.paused);
}

#[test]
fn timescale_fast_forward() {
    let ts = TimeScale::fastforward(10.0);
    assert!((ts.speedmultiplier - 10.0).abs() < 1e-9);
}

#[test]
fn timescale_slow_motion() {
    let ts = TimeScale::slowmotion(0.1);
    // slowmotion(factor) creates TimeScale::new(1.0/factor)
    assert!((ts.speedmultiplier - 10.0).abs() < 1e-6);
}

#[test]
fn timescale_step() {
    let mut ts = TimeScale::realtime();
    ts.step(1.0);
    assert!((ts.realtimes - 1.0).abs() < 1e-9);
    assert!((ts.simulationtimes - 1.0).abs() < 1e-9);
}

#[test]
fn timescale_step_fastforward() {
    let mut ts = TimeScale::fastforward(100.0);
    ts.step(1.0);
    assert!((ts.simulationtimes - 100.0).abs() < 1e-6);
}

#[test]
fn timescale_pause_resume() {
    let mut ts = TimeScale::realtime();
    ts.pause();
    assert!(ts.paused);
    ts.step(10.0);
    assert!(
        ts.simulationtimes.abs() < 1e-9,
        "No simulation time when paused"
    );
    ts.resume();
    assert!(!ts.paused);
    ts.step(1.0);
    assert!((ts.simulationtimes - 1.0).abs() < 1e-9);
}

#[test]
fn timescale_toggle_pause() {
    let mut ts = TimeScale::realtime();
    ts.togglepause();
    assert!(ts.paused);
    ts.togglepause();
    assert!(!ts.paused);
}

#[test]
fn timescale_set_speed() {
    let mut ts = TimeScale::realtime();
    ts.setspeed(50.0);
    assert!((ts.speedmultiplier - 50.0).abs() < 1e-9);
}

#[test]
fn timescale_simulation_dt() {
    let ts = TimeScale::fastforward(10.0);
    let sdt = ts.simulationdt(2.0);
    assert!((sdt - 20.0).abs() < 1e-6);
}

#[test]
fn timescale_sim_hours() {
    let mut ts = TimeScale::realtime();
    ts.step(3600.0);
    let h = ts.simhours();
    assert!((h - 1.0).abs() < 1e-6);
}

#[test]
fn timescale_sim_days() {
    let mut ts = TimeScale::realtime();
    ts.step(86400.0);
    let d = ts.simdays();
    assert!((d - 1.0).abs() < 1e-6);
}

#[test]
fn timescale_sim_neptune_days() {
    let mut ts = TimeScale::realtime();
    ts.step(NEPTUNEDAYSECONDS);
    let nd = ts.simneptunedays();
    assert!((nd - 1.0).abs() < 0.01);
}

#[test]
fn timescale_sim_neptune_years() {
    let mut ts = TimeScale::realtime();
    ts.step(100.0);
    let ny = ts.simneptuneyears();
    assert!(ny.is_finite());
    assert!(ny >= 0.0);
}
