//! Tests for PersistEnum derive macro.

use mad_hatter::PersistEnum;

// ── Basic enum (no other) ──

#[derive(Debug, Clone, PartialEq, Eq, PersistEnum)]
#[persist_enum(rename_all = "snake_case")]
pub enum Role {
    User,
    Agent,
    System,
}

// ── Enum with other fallback ──

#[derive(Debug, Clone, PartialEq, Eq, PersistEnum)]
#[persist_enum(rename_all = "snake_case")]
pub enum ActionType {
    Idle,
    ReadMsg,
    SendMsg,
    Script,
    #[persist_enum(other)]
    Other(String),
}

// ── Lowercase rename ──

#[derive(Debug, Clone, PartialEq, Eq, PersistEnum)]
#[persist_enum(rename_all = "lowercase")]
pub enum Priority {
    Low,
    Medium,
    High,
}

// ════════════════════════════════════════
// as_str() tests
// ════════════════════════════════════════

#[test]
fn test_as_str_basic() {
    assert_eq!(Role::User.as_str(), "user");
    assert_eq!(Role::Agent.as_str(), "agent");
    assert_eq!(Role::System.as_str(), "system");
}

#[test]
fn test_as_str_snake_case() {
    assert_eq!(ActionType::Idle.as_str(), "idle");
    assert_eq!(ActionType::ReadMsg.as_str(), "read_msg");
    assert_eq!(ActionType::SendMsg.as_str(), "send_msg");
    assert_eq!(ActionType::Script.as_str(), "script");
}

#[test]
fn test_as_str_other() {
    let custom = ActionType::Other("custom_action".to_string());
    assert_eq!(custom.as_str(), "custom_action");
}

#[test]
fn test_as_str_lowercase() {
    assert_eq!(Priority::Low.as_str(), "low");
    assert_eq!(Priority::Medium.as_str(), "medium");
    assert_eq!(Priority::High.as_str(), "high");
}

// ════════════════════════════════════════
// Display tests
// ════════════════════════════════════════

#[test]
fn test_display() {
    assert_eq!(format!("{}", Role::User), "user");
    assert_eq!(format!("{}", ActionType::ReadMsg), "read_msg");
    assert_eq!(format!("{}", ActionType::Other("xyz".to_string())), "xyz");
}

// ════════════════════════════════════════
// Serde tests
// ════════════════════════════════════════

#[test]
fn test_serde_serialize_basic() {
    let json = serde_json::to_string(&Role::User).unwrap();
    assert_eq!(json, "\"user\"");

    let json = serde_json::to_string(&Role::System).unwrap();
    assert_eq!(json, "\"system\"");
}

#[test]
fn test_serde_serialize_snake_case() {
    let json = serde_json::to_string(&ActionType::ReadMsg).unwrap();
    assert_eq!(json, "\"read_msg\"");
}

#[test]
fn test_serde_serialize_other() {
    let json = serde_json::to_string(&ActionType::Other("unknown".to_string())).unwrap();
    assert_eq!(json, "\"unknown\"");
}

#[test]
fn test_serde_deserialize_basic() {
    let role: Role = serde_json::from_str("\"agent\"").unwrap();
    assert_eq!(role, Role::Agent);
}

#[test]
fn test_serde_deserialize_snake_case() {
    let action: ActionType = serde_json::from_str("\"send_msg\"").unwrap();
    assert_eq!(action, ActionType::SendMsg);
}

#[test]
fn test_serde_deserialize_other_fallback() {
    let action: ActionType = serde_json::from_str("\"totally_new_action\"").unwrap();
    assert_eq!(action, ActionType::Other("totally_new_action".to_string()));
}

#[test]
fn test_serde_deserialize_unknown_no_other() {
    // Role has no #[persist_enum(other)], so unknown values should fail
    let result: Result<Role, _> = serde_json::from_str("\"unknown_role\"");
    assert!(result.is_err());
}

#[test]
fn test_serde_roundtrip() {
    let original = ActionType::ReadMsg;
    let json = serde_json::to_string(&original).unwrap();
    let deserialized: ActionType = serde_json::from_str(&json).unwrap();
    assert_eq!(original, deserialized);
}

#[test]
fn test_serde_roundtrip_other() {
    let original = ActionType::Other("custom".to_string());
    let json = serde_json::to_string(&original).unwrap();
    let deserialized: ActionType = serde_json::from_str(&json).unwrap();
    assert_eq!(original, deserialized);
}

// ════════════════════════════════════════
// sqlx database tests
// ════════════════════════════════════════

use sqlx::{sqlite::SqlitePool, FromRow};

#[derive(FromRow)]
struct RoleRow {
    id: i32,
    role: Role,
}

#[derive(FromRow)]
struct ActionRow {
    id: i32,
    action_type: ActionType,
}

async fn setup_pool() -> SqlitePool {
    let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
    sqlx::query("CREATE TABLE test_roles (id INTEGER PRIMARY KEY, role TEXT NOT NULL)")
        .execute(&pool)
        .await
        .unwrap();
    sqlx::query("CREATE TABLE test_actions (id INTEGER PRIMARY KEY, action_type TEXT NOT NULL)")
        .execute(&pool)
        .await
        .unwrap();
    pool
}

#[tokio::test]
async fn test_sqlx_insert_and_query_as_string() {
    let pool = setup_pool().await;

    sqlx::query("INSERT INTO test_roles (id, role) VALUES (?, ?)")
        .bind(1)
        .bind(&Role::Agent)
        .execute(&pool)
        .await
        .unwrap();

    let row: (i32, String) = sqlx::query_as("SELECT id, role FROM test_roles")
        .fetch_one(&pool)
        .await
        .unwrap();

    assert_eq!(row.1, "agent");
}

#[tokio::test]
async fn test_sqlx_roundtrip_basic() {
    let pool = setup_pool().await;

    sqlx::query("INSERT INTO test_roles (id, role) VALUES (?, ?)")
        .bind(1)
        .bind(&Role::User)
        .execute(&pool)
        .await
        .unwrap();

    let row: RoleRow = sqlx::query_as("SELECT id, role FROM test_roles")
        .fetch_one(&pool)
        .await
        .unwrap();

    assert_eq!(row.role, Role::User);
}

#[tokio::test]
async fn test_sqlx_roundtrip_with_other() {
    let pool = setup_pool().await;

    sqlx::query("INSERT INTO test_actions (id, action_type) VALUES (?, ?)")
        .bind(1)
        .bind(&ActionType::ReadMsg)
        .execute(&pool)
        .await
        .unwrap();

    sqlx::query("INSERT INTO test_actions (id, action_type) VALUES (?, ?)")
        .bind(2)
        .bind(&ActionType::Other("custom".to_string()))
        .execute(&pool)
        .await
        .unwrap();

    let rows: Vec<ActionRow> =
        sqlx::query_as("SELECT id, action_type FROM test_actions ORDER BY id")
            .fetch_all(&pool)
            .await
            .unwrap();

    assert_eq!(rows[0].action_type, ActionType::ReadMsg);
    assert_eq!(rows[1].action_type, ActionType::Other("custom".to_string()));
}

#[tokio::test]
async fn test_sqlx_unknown_value_with_other() {
    let pool = setup_pool().await;

    // Insert a raw string that doesn't match any known variant
    sqlx::query("INSERT INTO test_actions (id, action_type) VALUES (1, 'never_seen_before')")
        .execute(&pool)
        .await
        .unwrap();

    let row: ActionRow = sqlx::query_as("SELECT id, action_type FROM test_actions")
        .fetch_one(&pool)
        .await
        .unwrap();

    assert_eq!(
        row.action_type,
        ActionType::Other("never_seen_before".to_string())
    );
}

#[tokio::test]
async fn test_sqlx_unknown_value_no_other() {
    let pool = setup_pool().await;

    // Insert a raw string that doesn't match any Role variant
    sqlx::query("INSERT INTO test_roles (id, role) VALUES (1, 'unknown_role')")
        .execute(&pool)
        .await
        .unwrap();

    // Should fail because Role has no #[persist_enum(other)]
    let result: Result<RoleRow, _> = sqlx::query_as("SELECT id, role FROM test_roles")
        .fetch_one(&pool)
        .await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_sqlx_filter_by_enum() {
    let pool = setup_pool().await;

    sqlx::query("INSERT INTO test_roles (id, role) VALUES (?, ?)")
        .bind(1)
        .bind(&Role::User)
        .execute(&pool)
        .await
        .unwrap();
    sqlx::query("INSERT INTO test_roles (id, role) VALUES (?, ?)")
        .bind(2)
        .bind(&Role::Agent)
        .execute(&pool)
        .await
        .unwrap();
    sqlx::query("INSERT INTO test_roles (id, role) VALUES (?, ?)")
        .bind(3)
        .bind(&Role::System)
        .execute(&pool)
        .await
        .unwrap();

    let rows: Vec<RoleRow> =
        sqlx::query_as("SELECT id, role FROM test_roles WHERE role = ?")
            .bind(&Role::Agent)
            .fetch_all(&pool)
            .await
            .unwrap();

    assert_eq!(rows.len(), 1);
    assert_eq!(rows[0].id, 2);
    assert_eq!(rows[0].role, Role::Agent);
}