use rpstate::migration::ComponentOutcome;
use rpstate::store::builder::StoreBuilder;
use rpstate::{MigrationError, Store, migrate};
use rpstate_macros::rpstate;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tracing_test::traced_test;

mod identity_v1 {
    use super::*;

    #[rpstate(prefix = "complex_identity", version = 1)]
    pub struct Identity {
        #[state(default = "guest".to_string())]
        pub login: String,

        #[state(default = "free".to_string())]
        pub tier: String,

        #[state(default = "legacy-token".to_string())]
        pub legacy_token: String,
    }
}

#[rpstate(prefix = "complex_identity", version = 2)]
pub struct Identity {
    #[state(default = "guest".to_string())]
    pub username: String,

    #[state(default = "free".to_string())]
    pub plan: String,

    #[state(default = 0u64)]
    pub created_at_ms: u64,
}

mod workspace_v1 {
    use super::*;

    #[rpstate(prefix = "complex_workspace", version = 1)]
    pub struct Workspace {
        #[state(default = "Untitled".to_string())]
        pub title: String,

        #[state(default = "dark".to_string())]
        pub theme: String,

        #[state(default = true)]
        pub stale_flag: bool,
    }
}

mod workspace_v2 {
    use super::*;

    #[rpstate(prefix = "complex_workspace", version = 2)]
    pub struct Workspace {
        #[state(default = "Untitled".to_string())]
        pub name: String,

        #[state(default = "dark".to_string())]
        pub appearance_theme: String,
    }
}

#[rpstate(prefix = "complex_workspace", version = 3)]
pub struct Workspace {
    #[state(default = "Untitled".to_string())]
    pub name: String,

    #[state(default = "dark".to_string())]
    pub appearance_theme: String,

    #[state(default = "Welcome".to_string())]
    pub welcome_title: String,
}

mod telemetry_v1 {
    use super::*;

    #[rpstate(prefix = "complex_telemetry", version = 1)]
    pub struct Telemetry {
        #[state(default = true)]
        pub enabled: bool,

        #[state(default = 5u16)]
        pub sample_rate: u16,
    }
}

#[rpstate(prefix = "complex_telemetry", version = 2)]
pub struct Telemetry {
    #[state(default = true)]
    pub enabled: bool,

    #[state(default = 50u16)]
    pub sample_rate_per_mille: u16,
}

mod profile_v1 {
    use super::*;

    #[rpstate(prefix = "complex_profile", version = 1)]
    pub struct Profile {
        #[state(default = "".to_string())]
        pub full_name: String,

        #[state(default = "".to_string())]
        pub age_text: String,
    }
}

#[rpstate(prefix = "complex_profile", version = 2)]
pub struct Profile {
    #[state(default = "".to_string())]
    pub first_name: String,

    #[state(default = "".to_string())]
    pub last_name: String,

    #[state(default = None::<u8>)]
    pub age: Option<u8>,

    #[state(default = "free".to_string())]
    pub plan_snapshot: String,
}

mod ui_v1 {
    use super::*;

    #[rpstate(prefix = "complex_ui", version = 1)]
    pub struct Ui {
        #[state(default = 0u16)]
        pub sidebar_px: u16,

        #[state(default = 1u16)]
        pub width_px: u16,

        #[state(key = "panels.left.visible", default = true)]
        pub left_panel_visible: bool,
    }
}

#[rpstate(prefix = "complex_ui", version = 2)]
pub struct Ui {
    #[state(default = 0.25f32)]
    pub sidebar_ratio: f32,

    #[state(default = true)]
    pub left_panel_visible: bool,
}

mod shortcuts_v1 {
    use super::*;

    #[rpstate(prefix = "complex_shortcuts", version = 1)]
    pub struct Shortcuts {
        #[state(default = Vec::<String>::new())]
        pub legacy_bindings: Vec<String>,
    }
}

#[rpstate(prefix = "complex_shortcuts", version = 2)]
pub struct Shortcuts {
    #[state(default = Vec::<String>::new())]
    pub bindings: Vec<String>,
}

mod broken_root_v1 {
    use super::*;

    #[rpstate(prefix = "complex_broken_root", version = 1)]
    pub struct BrokenRoot {
        #[state(default = "stable".to_string())]
        pub original: String,
    }
}

#[rpstate(prefix = "complex_broken_root", version = 2)]
pub struct BrokenRoot {
    #[state(default = "stable".to_string())]
    pub original: String,

    #[state(default = false)]
    pub staged: bool,
}

mod broken_child_v1 {
    use super::*;

    #[rpstate(prefix = "complex_broken_child", version = 1)]
    pub struct BrokenChild {
        #[state(default = false)]
        pub fail: bool,
    }
}

#[rpstate(prefix = "complex_broken_child", version = 2)]
pub struct BrokenChild {
    #[state(default = false)]
    pub fail: bool,
}

migrate! {
    identity_v1::Identity_Data => Identity_Data,
    rename: [login => username, tier => plan],
    |old| {
        Ok(Self {
            username: old.login,
            plan: match old.tier.as_str() {
                "pro" => "professional".to_string(),
                other => other.to_string(),
            },
            created_at_ms: 1_700_000_000_000,
        })
    }
}

migrate! {
    workspace_v1::Workspace_Data => workspace_v2::Workspace_Data,
    rename: [title => name, theme => appearance_theme],
    |old| {
        Ok(Self {
            name: old.title,
            appearance_theme: old.theme,
        })
    }
}

migrate! {
    telemetry_v1::Telemetry_Data => Telemetry_Data,
    rename:[sample_rate => sample_rate_per_mille],
    |old| {
        Ok(Self {
            enabled: old.enabled,
            sample_rate_per_mille: old.sample_rate.saturating_mul(10),
        })
    }
}

fn unique_path(suffix: &str) -> std::path::PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("time is after epoch")
        .as_nanos();
    std::env::temp_dir().join(format!("rpstate-{suffix}-{nanos}.redb"))
}

#[cfg(feature = "redb")]
#[traced_test]
#[test]
fn complex_hybrid_migrations_handle_dependency_tree_and_rollback() {
    let path = unique_path("complex-migration");

    {
        let store = Arc::new(StoreBuilder::new(&path).build().unwrap());

        let identity = identity_v1::Identity::new(&store).unwrap();
        identity.login().set("ignat".to_string()).unwrap();
        identity.tier().set("pro".to_string()).unwrap();
        identity
            .legacy_token()
            .set("remove-me".to_string())
            .unwrap();

        let workspace = workspace_v1::Workspace::new(&store).unwrap();
        workspace
            .title()
            .set("Analytical Engine".to_string())
            .unwrap();
        workspace.theme().set("solarized".to_string()).unwrap();
        workspace.stale_flag().set(true).unwrap();

        let telemetry = telemetry_v1::Telemetry::new(&store).unwrap();
        telemetry.enabled().set(true).unwrap();
        telemetry.sample_rate().set(7u16).unwrap();

        let profile = profile_v1::Profile::new(&store).unwrap();
        profile.full_name().set("Ada Lovelace".to_string()).unwrap();
        profile.age_text().set("36".to_string()).unwrap();

        let ui = ui_v1::Ui::new(&store).unwrap();
        ui.sidebar_px().set(320u16).unwrap();
        ui.width_px().set(1280u16).unwrap();
        ui.left_panel_visible().set(false).unwrap();

        let shortcuts = shortcuts_v1::Shortcuts::new(&store).unwrap();
        shortcuts
            .legacy_bindings()
            .set(vec!["save=Ctrl+S".to_string(), "open=Ctrl+O".to_string()])
            .unwrap();

        let broken_root = broken_root_v1::BrokenRoot::new(&store).unwrap();
        broken_root.original().set("stable".to_string()).unwrap();

        let _broken_child = broken_child_v1::BrokenChild::new(&store).unwrap();
    }

    let (store, report) = StoreBuilder::new(&path)
        .migrations(|m| {
            m.collect_codegen();

            m.for_node::<Profile>().depends_on::<Identity>().step(
                2,
                "split full name and snapshot plan",
                |ctx| {
                    let full_name = ctx
                        .get::<String>("full_name")?
                        .expect("seed should contain profile full_name");
                    let mut parts = full_name.splitn(2, ' ');
                    let first_name = parts.next().unwrap_or_default().to_string();
                    let last_name = parts.next().unwrap_or_default().to_string();
                    let age = ctx
                        .get::<String>("age_text")?
                        .and_then(|value| value.parse::<u8>().ok());
                    let plan_snapshot = ctx
                        .global_get::<String>("complex_identity.plan")?
                        .expect("identity codegen migration should run first");

                    ctx.set("first_name", &first_name)?;
                    ctx.set("last_name", &last_name)?;
                    ctx.set("age", &age)?;
                    ctx.set("plan_snapshot", &plan_snapshot)?;
                    ctx.delete("full_name")?;
                    ctx.delete("age_text")?;
                    Ok(())
                },
            );

            m.for_node::<Workspace>().depends_on::<Profile>().step(
                3,
                "derive welcome title after profile migration",
                |ctx| {
                    let name = ctx
                        .get::<String>("name")?
                        .expect("workspace codegen migration should create name");
                    let first_name = ctx
                        .global_get::<String>("complex_profile.first_name")?
                        .expect("profile migration should create first_name");
                    let welcome_title = format!("{name} for {first_name}");
                    ctx.set("welcome_title", &welcome_title)?;
                    Ok(())
                },
            );

            m.for_node::<Ui>().depends_on::<Workspace>().step(
                2,
                "flatten panel state and normalize sidebar",
                |ctx| {
                    let sidebar_px = ctx.get::<u16>("sidebar_px")?.unwrap_or(0);
                    let width_px = ctx.get::<u16>("width_px")?.unwrap_or(1);
                    let sidebar_ratio = sidebar_px as f32 / width_px as f32;
                    let left_panel_visible =
                        ctx.get::<bool>("panels.left.visible")?.unwrap_or(true);
                    ctx.set("sidebar_ratio", &sidebar_ratio)?;
                    ctx.set("left_panel_visible", &left_panel_visible)?;
                    ctx.delete("sidebar_px")?;
                    ctx.delete("width_px")?;
                    ctx.delete("panels.left.visible")?;
                    Ok(())
                },
            );

            m.for_node::<Shortcuts>().depends_on::<Workspace>().step(
                2,
                "parse legacy shortcut bindings",
                |ctx| {
                    let legacy = ctx
                        .get::<Vec<String>>("legacy_bindings")?
                        .unwrap_or_default();
                    let mut bindings = legacy
                        .into_iter()
                        .filter_map(|entry| {
                            let (action, binding) = entry.split_once('=')?;
                            Some(format!("{action}:{binding}"))
                        })
                        .collect::<Vec<_>>();
                    bindings.sort();
                    ctx.set("bindings", &bindings)?;
                    ctx.delete("legacy_bindings")?;
                    Ok(())
                },
            );

            m.for_node::<BrokenRoot>()
                .step(2, "stage broken branch mutation", |ctx| {
                    ctx.set("original", &"mutated".to_string())?;
                    ctx.set("staged", &true)?;
                    Ok(())
                });

            m.for_node::<BrokenChild>().depends_on::<BrokenRoot>().step(
                2,
                "fail broken branch",
                |_| Err(MigrationError::Custom("intentional failure".into()).into()),
            );
        })
        .build()
        .unwrap();

    assert!(report.has_failures());

    assert_eq!(
        report
            .components
            .iter()
            .filter(|c| matches!(c.outcome, ComponentOutcome::Failed { .. }))
            .count(),
        1
    );

    assert!(logs_contain("✅ Applied: complex_identity v2"));
    assert!(logs_contain(
        "✅ Applied: complex_profile v2 (split full name and snapshot plan)"
    ));
    assert!(logs_contain(
        "✅ Applied: complex_workspace v3 (derive welcome title after profile migration)"
    ));
    assert!(logs_contain("✅ Applied: complex_telemetry v2"));
    assert!(logs_contain(
        "❌ Component [\"complex_broken_child\", \"complex_broken_root\"] failed: Migration error: intentional failure"
    ));
    assert!(logs_contain(
        "Transaction rolled back. Data for these prefixes remains unchanged."
    ));

    let identity = Identity::new(&store).unwrap();
    // AI-Doxxed-Driven Development
    assert_eq!(identity.username().get(), "ignat");
    assert_eq!(identity.plan().get(), "professional");
    assert_eq!(identity.created_at_ms().get(), 1_700_000_000_000);

    let profile = Profile::new(&store).unwrap();
    assert_eq!(profile.first_name().get(), "Ada");
    assert_eq!(profile.last_name().get(), "Lovelace");
    assert_eq!(profile.age().get(), Some(36));
    assert_eq!(profile.plan_snapshot().get(), "professional");

    let workspace = Workspace::new(&store).unwrap();
    assert_eq!(workspace.name().get(), "Analytical Engine");
    assert_eq!(workspace.appearance_theme().get(), "solarized");
    assert_eq!(workspace.welcome_title().get(), "Analytical Engine for Ada");

    let ui = Ui::new(&store).unwrap();
    assert!((ui.sidebar_ratio().get() - 0.25).abs() < f32::EPSILON);
    assert!(!ui.left_panel_visible().get());

    let shortcuts = Shortcuts::new(&store).unwrap();
    assert_eq!(
        shortcuts.bindings().get(),
        vec!["open:Ctrl+O".to_string(), "save:Ctrl+S".to_string()]
    );

    let telemetry = Telemetry::new(&store).unwrap();
    assert!(telemetry.enabled().get());
    assert_eq!(telemetry.sample_rate_per_mille().get(), 70);

    assert_eq!(store.get::<String>("complex_identity.login").unwrap(), None);
    assert_eq!(
        store
            .get::<String>("complex_identity.legacy_token")
            .unwrap(),
        None
    );
    assert_eq!(
        store.get::<String>("complex_profile.full_name").unwrap(),
        None
    );
    assert_eq!(
        store.get::<String>("complex_profile.age_text").unwrap(),
        None
    );
    assert_eq!(
        store.get::<String>("complex_workspace.title").unwrap(),
        None
    );
    assert_eq!(
        store.get::<String>("complex_workspace.stale_flag").unwrap(),
        None
    );
    assert_eq!(store.get::<u16>("complex_ui.sidebar_px").unwrap(), None);
    assert_eq!(
        store
            .get::<Vec<String>>("complex_shortcuts.legacy_bindings")
            .unwrap(),
        None
    );
    assert_eq!(
        store.get::<u16>("complex_telemetry.sample_rate").unwrap(),
        None
    );

    assert_eq!(
        store.get::<String>("complex_broken_root.original").unwrap(),
        Some("stable".to_string())
    );
    assert_eq!(
        store.get::<bool>("complex_broken_root.staged").unwrap(),
        None
    );
}
