use std::fs;
use std::sync::Arc;

use sshub::app::{App, AppDeps};
use sshub::config::AppConfig;
use sshub::metadata::MetadataDb;
use sshub::ssh::export_launcher_hosts_to;
use sshub::store::{HostSource, LauncherStore, NewHost};

#[path = "../support/mod.rs"]
mod support;

use support::{FixtureResolver, MockLauncher};

struct TestEnv {
    _config_dir: tempfile::TempDir,
    _data_dir: tempfile::TempDir,
    store: Arc<LauncherStore>,
    app: App,
}

impl TestEnv {
    fn new() -> Self {
        let config_dir = tempfile::tempdir().unwrap();
        let data_dir = tempfile::tempdir().unwrap();
        std::env::set_var("SSH_LAUNCHER_CONFIG_DIR", config_dir.path());
        std::env::set_var("SSH_LAUNCHER_DATA_DIR", data_dir.path());

        let store = Arc::new(LauncherStore::open(data_dir.path().join("launcher.db")).unwrap());
        let mut app = App::new_with_deps(
            AppConfig::default(),
            AppDeps {
                resolver: Box::new(FixtureResolver::from_manifest_dir()),
                metadata: Arc::new(MetadataDb::default()),
                store: Arc::clone(&store),
                launcher: Box::new(MockLauncher::new()),
                password_store: Box::new(sshub::credentials::NoopPasswordStore),
            },
        );
        app.reload_hosts().unwrap();

        Self {
            _config_dir: config_dir,
            _data_dir: data_dir,
            store,
            app,
        }
    }

    fn export_path(&self) -> std::path::PathBuf {
        self._config_dir.path().join("exported.conf")
    }
}

#[test]
fn import_ssh_config_upserts_fixture_hosts() {
    let mut env = TestEnv::new();

    let report = env.app.import_ssh_config().unwrap();
    assert_eq!(report.inserted, 3);
    assert_eq!(report.skipped_launcher, 0);

    let imported = env
        .store
        .list_hosts_filtered(Some(HostSource::SshConfig))
        .unwrap();
    assert_eq!(imported.len(), 3);
    assert!(imported.iter().any(|h| h.name == "dev-local"));
    assert!(imported.iter().any(|h| h.name == "staging-app"));
    assert!(imported.iter().any(|h| h.name == "prod-db-01"));

    let dev = env.store.get_host_by_name("dev-local").unwrap().unwrap();
    assert_eq!(dev.address, "localhost");
    assert_eq!(dev.source, HostSource::SshConfig);

    assert_eq!(env.app.hosts.len(), 3);
    assert!(env.app.hosts.iter().all(|h| h.managed().is_some()));
}

#[test]
fn import_does_not_overwrite_launcher_host_with_same_name() {
    let mut env = TestEnv::new();
    let default_id = env
        .store
        .get_identity_by_name("Default")
        .unwrap()
        .unwrap()
        .id;

    env.store
        .create_host(&NewHost {
            name: "dev-local".into(),
            label: Some("Launcher copy".into()),
            address: "192.168.99.1".into(),
            port: 2222,
            group_id: None,
            identity_id: Some(default_id),
            tags: vec!["launcher".into()],
            notes: None,
            ..Default::default()
        })
        .unwrap();
    env.app.reload_hosts().unwrap();

    let report = env.app.import_ssh_config().unwrap();
    assert_eq!(report.skipped_launcher, 1);
    assert_eq!(report.inserted, 2);

    let launcher = env.store.get_host_by_name("dev-local").unwrap().unwrap();
    assert_eq!(launcher.source, HostSource::Launcher);
    assert_eq!(launcher.address, "192.168.99.1");
    assert_eq!(launcher.port, 2222);
}

#[test]
fn export_launcher_hosts_roundtrip_with_backup() {
    let mut env = TestEnv::new();
    let default_id = env
        .store
        .get_identity_by_name("Default")
        .unwrap()
        .unwrap()
        .id;

    env.store
        .update_identity(
            default_id,
            &sshub::store::IdentityUpdate {
                username: Some(Some("admin".into())),
                private_key: Some(Some(std::path::PathBuf::from("/home/user/.ssh/id_ed25519"))),
                ..Default::default()
            },
        )
        .unwrap();

    env.store
        .create_host(&NewHost {
            name: "dev-partners".into(),
            label: Some("Dev Partners".into()),
            address: "10.100.19.123".into(),
            port: 22,
            group_id: None,
            identity_id: Some(default_id),
            tags: vec![],
            notes: None,
            ..Default::default()
        })
        .unwrap();

    env.app.reload_hosts().unwrap();

    let path = export_launcher_hosts_to(&env.store, &env.export_path()).unwrap();
    assert_eq!(path, env.export_path());

    let content = fs::read_to_string(&path).unwrap();
    assert!(content.contains("# Generated by SSHub"));
    assert!(content.contains("Host dev-partners"));
    assert!(content.contains("HostName 10.100.19.123"));
    assert!(content.contains("User admin"));
    assert!(content.contains("IdentityFile /home/user/.ssh/id_ed25519"));
    assert!(!content.contains("Host dev-local"));

    let updated_path = export_launcher_hosts_to(&env.store, &env.export_path()).unwrap();
    assert_eq!(updated_path, path);
    let bak = format!("{}.bak", path.display());
    assert!(fs::metadata(&bak).is_ok());
    assert_eq!(fs::read_to_string(&bak).unwrap(), content);
}

#[test]
fn import_then_reload_shows_db_ssh_config_hosts() {
    let mut env = TestEnv::new();

    env.app.import_ssh_config().unwrap();
    env.app.reload_hosts().unwrap();

    assert_eq!(env.app.hosts.len(), 3);
    assert!(env
        .app
        .hosts
        .iter()
        .all(|h| h.source() == HostSource::SshConfig));

    let staging = env.store.get_host_by_name("staging-app").unwrap().unwrap();
    assert_eq!(staging.address, "10.0.2.10");
}

#[test]
fn reimport_preserves_user_edited_tags_on_ssh_config_hosts() {
    let mut env = TestEnv::new();
    env.app.import_ssh_config().unwrap();

    let id = env.store.get_host_by_name("dev-local").unwrap().unwrap().id;
    env.store
        .update_host(
            id,
            &sshub::store::HostUpdate {
                tags: Some(vec!["kept".into()]),
                ..Default::default()
            },
        )
        .unwrap();

    let report = env.app.import_ssh_config().unwrap();
    assert_eq!(report.updated, 3);

    let dev = env.store.get_host_by_name("dev-local").unwrap().unwrap();
    assert!(dev.tags.iter().any(|t| t == "kept"));
    assert_eq!(dev.address, "localhost");
}
