//! Integration tests for rok-validate v2 derive macros.
//! Run as a separate crate so ::rok_validate:: paths resolve correctly.

use rok_validate::{Filterable, Sortable, Validate, ValidationErrors};

// ── Nested Validate ───────────────────────────────────────────────────────────

#[derive(Validate)]
struct Address {
    #[validate(required)]
    pub zip: String,
}

#[derive(Validate)]
struct Order {
    #[validate(nested)]
    pub address: Address,
}

#[test]
fn nested_struct_error_path() {
    let o = Order {
        address: Address {
            zip: "".to_string(),
        },
    };
    let err = o.validate().unwrap_err();
    let inner = err.into_inner();
    assert!(
        inner.contains_key("address.zip"),
        "key should be address.zip, got: {:?}",
        inner.keys().collect::<Vec<_>>()
    );
}

#[test]
fn nested_struct_valid() {
    let o = Order {
        address: Address {
            zip: "12345".to_string(),
        },
    };
    assert!(o.validate().is_ok());
}

// ── Nested Vec<T: Validate> ───────────────────────────────────────────────────

#[derive(Validate)]
struct Item {
    #[validate(min = 1)]
    pub quantity: i32,
}

#[derive(Validate)]
struct Cart {
    #[validate(nested)]
    pub items: Vec<Item>,
}

#[test]
fn nested_vec_error_path() {
    let c = Cart {
        items: vec![Item { quantity: 5 }, Item { quantity: 0 }],
    };
    let err = c.validate().unwrap_err();
    let inner = err.into_inner();
    assert!(
        inner.contains_key("items[1].quantity"),
        "key should be items[1].quantity, got: {:?}",
        inner.keys().collect::<Vec<_>>()
    );
    assert!(!inner.contains_key("items[0].quantity"));
}

// ── min_items / max_items ─────────────────────────────────────────────────────

#[derive(Validate)]
struct Batch {
    #[validate(min_items = 1, max_items = 3)]
    pub ids: Vec<i32>,
}

#[test]
fn min_items_fails_empty() {
    let b = Batch { ids: vec![] };
    let err = b.validate().unwrap_err();
    assert!(err.into_inner().contains_key("ids"));
}

#[test]
fn max_items_fails_overflow() {
    let b = Batch {
        ids: vec![1, 2, 3, 4],
    };
    let err = b.validate().unwrap_err();
    assert!(err.into_inner().contains_key("ids"));
}

#[test]
fn items_in_range_passes() {
    let b = Batch { ids: vec![1, 2] };
    assert!(b.validate().is_ok());
}

// ── Filterable ────────────────────────────────────────────────────────────────

#[derive(Filterable)]
struct UserFilters {
    pub role: Option<String>,
    #[filter(skip)]
    pub page: Option<u32>,
}

#[test]
fn filterable_eq_where_clause() {
    use rok_orm_core::QueryBuilder;

    let f = UserFilters {
        role: Some("admin".to_string()),
        page: Some(1),
    };
    let q: QueryBuilder<()> = QueryBuilder::new("users");
    let q = f.apply_filters(q);
    let (sql, params) = q.to_sql();
    assert!(sql.contains("WHERE"), "should have WHERE clause");
    assert!(sql.contains("role"), "should filter by role");
    assert!(!sql.contains("page"), "skipped field must not appear");
    assert_eq!(params.len(), 1);
}

#[derive(Filterable)]
struct NameFilter {
    #[filter(like)]
    pub name: Option<String>,
}

#[test]
fn filterable_like_wraps_percent() {
    use rok_orm_core::QueryBuilder;

    let f = NameFilter {
        name: Some("alice".to_string()),
    };
    let q: QueryBuilder<()> = QueryBuilder::new("users");
    let q = f.apply_filters(q);
    let (sql, _) = q.to_sql();
    assert!(sql.contains("LIKE"), "should use LIKE");
}

#[derive(Filterable)]
struct RoleFilter {
    pub role: Option<String>,
}

#[test]
fn filterable_none_skips_condition() {
    use rok_orm_core::QueryBuilder;

    let f = RoleFilter { role: None };
    let q: QueryBuilder<()> = QueryBuilder::new("users");
    let q = f.apply_filters(q);
    let (sql, params) = q.to_sql();
    assert!(!sql.contains("WHERE"), "None fields should not add WHERE");
    assert!(params.is_empty());
}

#[derive(Filterable)]
struct AgeFilter {
    #[filter(gte, column = "age")]
    pub min_age: Option<i32>,
    #[filter(lte, column = "age")]
    pub max_age: Option<i32>,
}

#[test]
fn filterable_gte_lte() {
    use rok_orm_core::QueryBuilder;

    let f = AgeFilter {
        min_age: Some(18),
        max_age: Some(65),
    };
    let q: QueryBuilder<()> = QueryBuilder::new("users");
    let q = f.apply_filters(q);
    let (sql, params) = q.to_sql();
    assert!(sql.contains(">="), "should have >=");
    assert!(sql.contains("<="), "should have <=");
    assert_eq!(params.len(), 2);
}

// ── Sortable ──────────────────────────────────────────────────────────────────

#[derive(Sortable)]
#[sortable(allow = ["created_at", "email"])]
struct UserSort {
    pub sort: Option<String>,
}

#[test]
fn sortable_applies_order_by() {
    use rok_orm_core::QueryBuilder;

    let s = UserSort {
        sort: Some("-created_at,email".to_string()),
    };
    let q: QueryBuilder<()> = QueryBuilder::new("users");
    let q = s.apply_sort(q);
    let (sql, _) = q.to_sql();
    assert!(sql.contains("ORDER BY"), "should have ORDER BY");
    assert!(sql.contains("created_at DESC"), "created_at should be DESC");
    assert!(sql.contains("email"), "email should be present");
}

#[derive(Sortable)]
#[sortable(allow = ["email"])]
struct UserSortRestricted {
    pub sort: Option<String>,
}

#[test]
fn sortable_drops_unknown_columns() {
    use rok_orm_core::QueryBuilder;

    let s = UserSortRestricted {
        sort: Some("injected,email".to_string()),
    };
    let q: QueryBuilder<()> = QueryBuilder::new("users");
    let q = s.apply_sort(q);
    let (sql, _) = q.to_sql();
    assert!(
        !sql.contains("injected"),
        "unknown column should be dropped"
    );
    assert!(sql.contains("email"), "allowed column should be present");
}

#[derive(Sortable)]
#[sortable(allow = ["email"])]
struct UserSortEmpty {
    pub sort: Option<String>,
}

#[test]
fn sortable_none_no_order() {
    use rok_orm_core::QueryBuilder;

    let s = UserSortEmpty { sort: None };
    let q: QueryBuilder<()> = QueryBuilder::new("users");
    let q = s.apply_sort(q);
    let (sql, _) = q.to_sql();
    assert!(
        !sql.contains("ORDER BY"),
        "None sort should not add ORDER BY"
    );
}

// ── ValidationErrors helpers (tested here for completeness) ──────────────────

#[test]
fn errors_prefix_and_merge() {
    let mut a = ValidationErrors::new();
    a.add("zip", "required");
    let prefixed = a.prefix("address");

    let mut b = ValidationErrors::new();
    b.add("email", "invalid");
    b.merge(prefixed);

    let inner = b.into_inner();
    assert!(inner.contains_key("address.zip"));
    assert!(inner.contains_key("email"));
}
