//! Integration tests for FormatEnum derive macro.
//!
//! Contract covered:
//! - as_str() + Display map to renamed variant strings
//! - serde Serialize/Deserialize roundtrip (JSON)
//! - Unknown variant errors (when no #[format_enum(other)] fallback)
//! - #[format_enum(other)] catches any unknown string
//! - sqlx Encode/Decode through a real in-memory SQLite connection
//! - rename_all = "lowercase" and "snake_case"

use mad_hatter::FormatEnum;
use sqlx::SqlitePool;

// ============================================================
// Fixture types
// ============================================================

#[derive(Debug, Clone, PartialEq, Eq, FormatEnum)]
#[format_enum(rename_all = "snake_case")]
enum ActionKind {
    ReadMsg,
    SendMsg,
    Idle,
}

#[derive(Debug, Clone, PartialEq, Eq, FormatEnum)]
#[format_enum(rename_all = "lowercase")]
enum Level {
    Info,
    Warn,
    Error,
}

#[derive(Debug, Clone, PartialEq, Eq, FormatEnum)]
#[format_enum(rename_all = "snake_case")]
enum Event {
    Start,
    Stop,
    #[format_enum(other)]
    Custom(String),
}

// ============================================================
// as_str / Display
// ============================================================

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

#[test]
fn test_as_str_lowercase() {
    assert_eq!(Level::Info.as_str(), "info");
    assert_eq!(Level::Warn.as_str(), "warn");
    assert_eq!(Level::Error.as_str(), "error");
}

#[test]
fn test_display_equals_as_str() {
    assert_eq!(format!("{}", ActionKind::ReadMsg), "read_msg");
    assert_eq!(format!("{}", Level::Warn), "warn");
}

#[test]
fn test_other_variant_as_str_returns_inner() {
    let e = Event::Custom("foo_bar".to_string());
    assert_eq!(e.as_str(), "foo_bar");
    assert_eq!(format!("{}", e), "foo_bar");
}

// ============================================================
// serde roundtrip
// ============================================================

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

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

#[test]
fn test_serde_roundtrip_all_variants() {
    for v in [ActionKind::ReadMsg, ActionKind::SendMsg, ActionKind::Idle] {
        let j = serde_json::to_string(&v).unwrap();
        let back: ActionKind = serde_json::from_str(&j).unwrap();
        assert_eq!(back, v);
    }
}

#[test]
fn test_serde_unknown_variant_errors_without_other() {
    let result: Result<ActionKind, _> = serde_json::from_str("\"unknown\"");
    assert!(result.is_err(), "should reject unknown variant");
    let msg = format!("{}", result.unwrap_err());
    assert!(
        msg.contains("unknown") || msg.contains("variant"),
        "error should mention unknown/variant, got: {}",
        msg
    );
}

#[test]
fn test_serde_other_catches_unknown() {
    let v: Event = serde_json::from_str("\"something_else\"").unwrap();
    assert_eq!(v, Event::Custom("something_else".to_string()));
}

#[test]
fn test_serde_other_roundtrip() {
    let original = Event::Custom("my_custom_event".to_string());
    let j = serde_json::to_string(&original).unwrap();
    assert_eq!(j, "\"my_custom_event\"");
    let back: Event = serde_json::from_str(&j).unwrap();
    assert_eq!(back, original);
}

#[test]
fn test_serde_other_does_not_shadow_known_variants() {
    let v: Event = serde_json::from_str("\"start\"").unwrap();
    assert_eq!(v, Event::Start);
    let v: Event = serde_json::from_str("\"stop\"").unwrap();
    assert_eq!(v, Event::Stop);
}

// ============================================================
// sqlx Encode/Decode through in-memory SQLite
// ============================================================

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

#[derive(Debug, sqlx::FromRow)]
struct EventRow {
    #[allow(dead_code)]
    id: i32,
    kind: ActionKind,
}

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

    for kind in [ActionKind::ReadMsg, ActionKind::SendMsg, ActionKind::Idle] {
        sqlx::query("INSERT INTO events (kind) VALUES (?)")
            .bind(&kind)
            .execute(&pool)
            .await
            .unwrap();
    }

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

    assert_eq!(rows.len(), 3);
    assert_eq!(rows[0].kind, ActionKind::ReadMsg);
    assert_eq!(rows[1].kind, ActionKind::SendMsg);
    assert_eq!(rows[2].kind, ActionKind::Idle);
}

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

    sqlx::query("INSERT INTO events (kind) VALUES (?)")
        .bind(&ActionKind::ReadMsg)
        .execute(&pool)
        .await
        .unwrap();

    // Read raw TEXT to verify actual storage format.
    let raw: (String,) = sqlx::query_as("SELECT kind FROM events WHERE id = 1")
        .fetch_one(&pool)
        .await
        .unwrap();
    assert_eq!(raw.0, "read_msg");
}

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

    sqlx::query("INSERT INTO events (kind) VALUES (?)")
        .bind("mystery")
        .execute(&pool)
        .await
        .unwrap();

    let result: Result<EventRow, _> = sqlx::query_as("SELECT id, kind FROM events WHERE id = 1")
        .fetch_one(&pool)
        .await;

    assert!(result.is_err(), "should fail to decode unknown variant");
}

#[tokio::test]
async fn test_sqlx_other_variant_catches_unknown() {
    let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
    sqlx::query("CREATE TABLE ev2 (id INTEGER PRIMARY KEY AUTOINCREMENT, e TEXT NOT NULL)")
        .execute(&pool)
        .await
        .unwrap();

    sqlx::query("INSERT INTO ev2 (e) VALUES (?)")
        .bind(&Event::Start)
        .execute(&pool)
        .await
        .unwrap();
    sqlx::query("INSERT INTO ev2 (e) VALUES (?)")
        .bind("weird_thing")
        .execute(&pool)
        .await
        .unwrap();

    #[derive(Debug, sqlx::FromRow)]
    struct Ev2Row {
        #[allow(dead_code)]
        id: i32,
        e: Event,
    }

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

    assert_eq!(rows.len(), 2);
    assert_eq!(rows[0].e, Event::Start);
    assert_eq!(rows[1].e, Event::Custom("weird_thing".to_string()));
}