//! Integration tests for shepherdd
//!
//! These tests verify the end-to-end behavior of shepherdd.

use shepherd_api::{EntryKind, WarningSeverity, WarningThreshold};
use shepherd_config::{AvailabilityPolicy, Entry, LimitsPolicy, Policy};
use shepherd_core::{CoreEngine, CoreEvent, LaunchDecision};
use shepherd_host_api::{HostCapabilities, MockHost};
use shepherd_store::{SqliteStore, Store};
use shepherd_util::{self, EntryId, MonotonicInstant};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;

fn make_test_policy() -> Policy {
    Policy {
        service: Default::default(),
        entries: vec![Entry {
            id: EntryId::new("test-game"),
            label: "Test Game".into(),
            icon_ref: None,
            kind: EntryKind::Process {
                command: "sleep".into(),
                args: vec!["999".into()],
                env: HashMap::new(),
                cwd: None,
            },
            availability: AvailabilityPolicy {
                windows: vec![],
                always: true,
            },
            limits: LimitsPolicy {
                max_run: Some(Duration::from_secs(10)), // Short for testing
                daily_quota: None,
                cooldown: None,
            },
            warnings: vec![
                WarningThreshold {
                    seconds_before: 5,
                    severity: WarningSeverity::Warn,
                    message_template: Some("5 seconds left".into()),
                },
                WarningThreshold {
                    seconds_before: 2,
                    severity: WarningSeverity::Critical,
                    message_template: Some("2 seconds left!".into()),
                },
            ],
            volume: None,
            disabled: false,
            disabled_reason: None,
            internet: Default::default(),
        }],
        default_warnings: vec![],
        default_max_run: Some(Duration::from_secs(3600)),
        volume: Default::default(),
    }
}

#[test]
fn test_policy_loading() {
    let policy = make_test_policy();
    assert_eq!(policy.entries.len(), 1);
    assert_eq!(policy.entries[0].id.as_str(), "test-game");
}

#[test]
fn test_entry_listing() {
    let policy = make_test_policy();
    let store = Arc::new(SqliteStore::in_memory().unwrap());
    let caps = HostCapabilities::minimal();
    let engine = CoreEngine::new(policy, store, caps);

    let entries = engine.list_entries(shepherd_util::now());

    assert_eq!(entries.len(), 1);
    assert!(entries[0].enabled);
    assert_eq!(entries[0].entry_id.as_str(), "test-game");
    assert!(entries[0].max_run_if_started_now.is_some());
}

#[test]
fn test_launch_approval() {
    let policy = make_test_policy();
    let store = Arc::new(SqliteStore::in_memory().unwrap());
    let caps = HostCapabilities::minimal();
    let engine = CoreEngine::new(policy, store, caps);

    let entry_id = EntryId::new("test-game");
    let decision = engine.request_launch(&entry_id, shepherd_util::now());

    assert!(
        matches!(decision, LaunchDecision::Approved(plan) if plan.max_duration == Some(Duration::from_secs(10)))
    );
}

#[test]
fn test_session_lifecycle() {
    let policy = make_test_policy();
    let store = Arc::new(SqliteStore::in_memory().unwrap());
    let caps = HostCapabilities::minimal();
    let mut engine = CoreEngine::new(policy, store, caps);

    let entry_id = EntryId::new("test-game");
    let now = shepherd_util::now();
    let now_mono = MonotonicInstant::now();

    // Launch
    let plan = match engine.request_launch(&entry_id, now) {
        LaunchDecision::Approved(p) => p,
        LaunchDecision::Denied { .. } => panic!("Launch should be approved"),
    };

    // Start session
    let event = engine.start_session(plan, now, now_mono);
    assert!(matches!(event, CoreEvent::SessionStarted { .. }));

    // Verify session is active
    assert!(engine.has_active_session());

    // Second launch should be denied
    let decision = engine.request_launch(&entry_id, now);
    assert!(matches!(decision, LaunchDecision::Denied { .. }));
}

#[test]
fn test_warning_emission() {
    let policy = make_test_policy();
    let store = Arc::new(SqliteStore::in_memory().unwrap());
    let caps = HostCapabilities::minimal();
    let mut engine = CoreEngine::new(policy, store, caps);

    let entry_id = EntryId::new("test-game");
    let now = shepherd_util::now();
    let now_mono = MonotonicInstant::now();

    // Start session
    let plan = match engine.request_launch(&entry_id, now) {
        LaunchDecision::Approved(p) => p,
        _ => panic!(),
    };
    engine.start_session(plan, now, now_mono);

    // No warnings at start
    let events = engine.tick(now_mono, now);
    assert!(events.is_empty());

    // At 6 seconds (4 seconds remaining), 5-second warning should fire
    let at_6s_mono = now_mono + Duration::from_secs(6);
    let at_6s = now + chrono::Duration::seconds(6);
    let events = engine.tick(at_6s_mono, at_6s);
    assert_eq!(events.len(), 1);
    assert!(matches!(
        &events[0],
        CoreEvent::Warning {
            threshold_seconds: 5,
            ..
        }
    ));

    // At 9 seconds (1 second remaining), 2-second warning should fire
    let at_9s_mono = now_mono + Duration::from_secs(9);
    let at_9s = now + chrono::Duration::seconds(9);
    let events = engine.tick(at_9s_mono, at_9s);
    assert_eq!(events.len(), 1);
    assert!(matches!(
        &events[0],
        CoreEvent::Warning {
            threshold_seconds: 2,
            ..
        }
    ));

    // Warnings shouldn't repeat
    let events = engine.tick(at_9s_mono, at_9s);
    assert!(events.is_empty());
}

#[test]
fn test_session_expiry() {
    let policy = make_test_policy();
    let store = Arc::new(SqliteStore::in_memory().unwrap());
    let caps = HostCapabilities::minimal();
    let mut engine = CoreEngine::new(policy, store, caps);

    let entry_id = EntryId::new("test-game");
    let now = shepherd_util::now();
    let now_mono = MonotonicInstant::now();

    // Start session
    let plan = match engine.request_launch(&entry_id, now) {
        LaunchDecision::Approved(p) => p,
        _ => panic!(),
    };
    engine.start_session(plan, now, now_mono);

    // At 11 seconds, session should be expired
    let at_11s_mono = now_mono + Duration::from_secs(11);
    let at_11s = now + chrono::Duration::seconds(11);
    let events = engine.tick(at_11s_mono, at_11s);

    // Should have both remaining warnings + expiry
    let has_expiry = events
        .iter()
        .any(|e| matches!(e, CoreEvent::ExpireDue { .. }));
    assert!(has_expiry, "Expected ExpireDue event");
}

#[test]
fn test_usage_accounting() {
    let policy = make_test_policy();
    let store = Arc::new(SqliteStore::in_memory().unwrap());
    let store_check = store.clone();
    let caps = HostCapabilities::minimal();
    let mut engine = CoreEngine::new(policy, store, caps);

    let entry_id = EntryId::new("test-game");
    let now = shepherd_util::now();
    let now_mono = MonotonicInstant::now();

    // Start session
    let plan = match engine.request_launch(&entry_id, now) {
        LaunchDecision::Approved(p) => p,
        _ => panic!(),
    };
    engine.start_session(plan, now, now_mono);

    // Simulate 5 seconds passing
    let later_mono = now_mono + Duration::from_secs(5);
    let later = now + chrono::Duration::seconds(5);

    // Session exits
    engine.notify_session_exited(Some(0), later_mono, later);

    // Check usage was recorded
    let usage = store_check.get_usage(&entry_id, now.date_naive()).unwrap();
    assert!(usage >= Duration::from_secs(4) && usage <= Duration::from_secs(6));
}

#[tokio::test]
async fn test_mock_host_integration() {
    use shepherd_host_api::{HostAdapter, SpawnOptions};
    use shepherd_util::SessionId;

    let host = MockHost::new();
    let _rx = host.subscribe();

    let session_id = SessionId::new();
    let entry = EntryKind::Process {
        command: "test".into(),
        args: vec![],
        env: HashMap::new(),
        cwd: None,
    };

    // Spawn
    let handle = host
        .spawn(session_id.clone(), &entry, SpawnOptions::default())
        .await
        .unwrap();

    // Verify running
    assert_eq!(host.running_sessions().len(), 1);

    // Stop
    host.stop(
        &handle,
        shepherd_host_api::StopMode::Graceful {
            timeout: Duration::from_secs(1),
        },
    )
    .await
    .unwrap();
}

#[test]
fn test_config_parsing() {
    use shepherd_config::parse_config;

    let config = r#"
        config_version = 1

        [[entries]]
        id = "scummvm"
        label = "ScummVM"
        kind = { type = "process", command = "scummvm", args = ["-f"] }

        [entries.availability]
        [[entries.availability.windows]]
        days = "weekdays"
        start = "14:00"
        end = "18:00"

        [entries.limits]
        max_run_seconds = 3600
        daily_quota_seconds = 7200
        cooldown_seconds = 300

        [[entries.warnings]]
        seconds_before = 300
        severity = "info"
        message = "5 minutes remaining"
    "#;

    let policy = parse_config(config).unwrap();
    assert_eq!(policy.entries.len(), 1);
    assert_eq!(policy.entries[0].id.as_str(), "scummvm");
    assert_eq!(
        policy.entries[0].limits.max_run,
        Some(Duration::from_secs(3600))
    );
    assert_eq!(
        policy.entries[0].limits.daily_quota,
        Some(Duration::from_secs(7200))
    );
    assert_eq!(
        policy.entries[0].limits.cooldown,
        Some(Duration::from_secs(300))
    );
    assert_eq!(policy.entries[0].warnings.len(), 1);
}

#[test]
fn test_session_extension() {
    let policy = make_test_policy();
    let store = Arc::new(SqliteStore::in_memory().unwrap());
    let caps = HostCapabilities::minimal();
    let mut engine = CoreEngine::new(policy, store, caps);

    let entry_id = EntryId::new("test-game");
    let now = shepherd_util::now();
    let now_mono = MonotonicInstant::now();

    // Start session
    let plan = match engine.request_launch(&entry_id, now) {
        LaunchDecision::Approved(p) => p,
        _ => panic!(),
    };
    engine.start_session(plan, now, now_mono);

    // Get original deadline (should be Some for this test)
    let original_deadline = engine
        .current_session()
        .unwrap()
        .deadline
        .expect("Expected deadline");

    // Extend by 5 minutes
    let new_deadline = engine.extend_current(Duration::from_secs(300), now_mono, now);
    assert!(new_deadline.is_some());

    let new_deadline = new_deadline.unwrap();
    let extension = new_deadline.signed_duration_since(original_deadline);
    assert!(extension.num_seconds() >= 299 && extension.num_seconds() <= 301);
}
