#![allow(unused_imports, dead_code)]

mod common;

use crate::common::TestContext;
use sea_orm::{
    Database, DbConn, DbErr,
    entity::*,
    query::*,
    sea_query::{Expr, Query},
};

mod one {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "one")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        #[sea_orm(has_one)]
        pub two: HasOne<super::two::Entity>,
        #[sea_orm(has_one)]
        pub six: HasOne<super::six::Entity>,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod two {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "two")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        pub one_id: i32,
        pub three_id: i32,
        #[sea_orm(belongs_to, from = "one_id", to = "id")]
        pub one: BelongsTo<super::one::Entity>,
        #[sea_orm(belongs_to, from = "three_id", to = "id")]
        pub three: BelongsTo<super::three::Entity>,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod three {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "three")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        #[sea_orm(has_one)]
        pub four: HasOne<super::four::Entity>,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod four {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "four")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        pub three_id: Option<i32>,
        #[sea_orm(belongs_to, from = "three_id", to = "id")]
        pub three: BelongsTo<Option<super::three::Entity>>,
        #[sea_orm(has_one)]
        pub five: HasOne<super::five::Entity>,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod five {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "five")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        pub four_id: i32,
        #[sea_orm(belongs_to, from = "four_id", to = "id")]
        pub four: BelongsTo<super::four::Entity>,
        #[sea_orm(has_one)]
        pub six: HasOne<super::six::Entity>,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod six {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "six")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        pub one_id: i32,
        pub five_id: i32,
        #[sea_orm(belongs_to, from = "one_id", to = "id")]
        pub one: BelongsTo<super::one::Entity>,
        #[sea_orm(belongs_to, from = "five_id", to = "id")]
        pub five: BelongsTo<super::five::Entity>,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod composite_a {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "composite_a")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        #[sea_orm(unique_key = "pair")]
        pub left_id: i32,
        #[sea_orm(unique_key = "pair")]
        pub right_id: i32,
        #[sea_orm(has_one)]
        pub b: HasOne<super::composite_b::Entity>,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod composite_b {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "composite_b")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        pub left_id: i32,
        pub right_id: i32,
        #[sea_orm(belongs_to, from = "(left_id, right_id)", to = "(left_id, right_id)")]
        pub a: BelongsTo<super::composite_a::Entity>,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod composite_b_without_index {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "composite_b")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        pub left_id: i32,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod composite_c {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "composite_c")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        #[sea_orm(unique_key = "season_episode")]
        pub season_number: i32,
        #[sea_orm(unique_key = "season_episode")]
        pub episode_number: i32,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

mod unique_item {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "unique_item")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        #[sea_orm(unique)]
        pub name: String,
        #[sea_orm(unique_key = "code")]
        pub code: i32,
    }

    impl ActiveModelBehavior for ActiveModel {}
}

#[sea_orm_macros::test]
fn test_select_six() -> Result<(), DbErr> {
    let ctx = TestContext::new("test_select_six");
    let db = &ctx.db;

    db.get_schema_builder()
        .register(one::Entity)
        .register(two::Entity)
        .register(three::Entity) // topologically three ranks higher than two
        .register(four::Entity)
        .register(five::Entity)
        .register(six::Entity)
        .apply(db)?;

    one::ActiveModel { id: Set(1) }.insert(db)?;
    one::ActiveModel { id: Set(11) }.insert(db)?;

    three::ActiveModel { id: Set(3) }.insert(db)?;
    three::ActiveModel { id: Set(33) }.insert(db)?;

    two::ActiveModel {
        id: Set(2),
        one_id: Set(1),
        three_id: Set(3),
    }
    .insert(db)?;

    four::ActiveModel {
        id: Set(4),
        three_id: Set(Some(3)),
    }
    .insert(db)?;

    four::ActiveModel {
        id: Set(44),
        three_id: Set(None),
    }
    .insert(db)?;

    five::ActiveModel {
        id: Set(5),
        four_id: Set(4),
    }
    .insert(db)?;

    five::ActiveModel {
        id: Set(55),
        four_id: Set(44),
    }
    .insert(db)?;

    six::ActiveModel {
        id: Set(6),
        one_id: Set(1),
        five_id: Set(55),
    }
    .insert(db)?;

    let one = one::Entity::find().order_by_id_asc().one(db)?.unwrap();
    assert_eq!(one.id, 1);

    let two = two::Entity::find().order_by_id_asc().one(db)?.unwrap();
    assert_eq!(two.id, 2);

    let three = three::Entity::find().order_by_id_asc().one(db)?.unwrap();
    assert_eq!(three.id, 3);

    let four = four::Entity::find().order_by_id_asc().one(db)?.unwrap();
    assert_eq!(four.id, 4);

    let five = five::Entity::find().order_by_id_asc().one(db)?.unwrap();
    assert_eq!(five.id, 5);

    let six = six::Entity::find().order_by_id_asc().one(db)?.unwrap();
    assert_eq!(six.id, 6);

    let (one, two) = one::Entity::find()
        .find_also(one::Entity, two::Entity)
        .one(db)?
        .unwrap();
    assert_eq!(one.id, 1);
    assert_eq!(two.unwrap().id, 2);

    let (one, two, three) = one::Entity::find()
        .find_also(one::Entity, two::Entity)
        .find_also(two::Entity, three::Entity)
        .one(db)?
        .unwrap();
    assert_eq!(one.id, 1);
    assert_eq!(two.unwrap().id, 2);
    assert_eq!(three.unwrap().id, 3);

    let (two, one, three) = two::Entity::find()
        .find_also_related(one::Entity)
        .find_also_related(three::Entity)
        .one(db)?
        .unwrap();
    assert_eq!(one.unwrap().id, 1);
    assert_eq!(two.id, 2);
    assert_eq!(three.unwrap().id, 3);

    let (one, two, three, four) = one::Entity::find()
        .find_also(one::Entity, two::Entity)
        .find_also(two::Entity, three::Entity)
        .find_also(three::Entity, four::Entity)
        .one(db)?
        .unwrap();
    assert_eq!(one.id, 1);
    assert_eq!(two.unwrap().id, 2);
    assert_eq!(three.unwrap().id, 3);
    assert_eq!(four.unwrap().id, 4);

    let (one, two, three, six) = one::Entity::find()
        .find_also(one::Entity, two::Entity)
        .find_also(two::Entity, three::Entity)
        .find_also(one::Entity, six::Entity)
        .one(db)?
        .unwrap();
    assert_eq!(one.id, 1);
    assert_eq!(two.unwrap().id, 2);
    assert_eq!(three.unwrap().id, 3);
    assert_eq!(six.unwrap().id, 6);

    let (one, two, three, four, five) = one::Entity::find()
        .find_also(one::Entity, two::Entity)
        .find_also(two::Entity, three::Entity)
        .find_also(three::Entity, four::Entity)
        .find_also(four::Entity, five::Entity)
        .one(db)?
        .unwrap();
    assert_eq!(one.id, 1);
    assert_eq!(two.unwrap().id, 2);
    assert_eq!(three.unwrap().id, 3);
    assert_eq!(four.unwrap().id, 4);
    assert_eq!(five.unwrap().id, 5);

    let (one, two, three, four, five, six) = one::Entity::find()
        .find_also(one::Entity, two::Entity)
        .find_also(two::Entity, three::Entity)
        .find_also(three::Entity, four::Entity)
        .find_also(four::Entity, five::Entity)
        .find_also(one::Entity, six::Entity)
        .one(db)?
        .unwrap();
    assert_eq!(one.id, 1);
    assert_eq!(two.unwrap().id, 2);
    assert_eq!(three.unwrap().id, 3);
    assert_eq!(four.unwrap().id, 4);
    assert_eq!(five.unwrap().id, 5);
    assert_eq!(six.unwrap().id, 6);

    let (six, five, four) = six::Entity::find_by_id(6)
        .find_also_related(five::Entity)
        .and_also_related(four::Entity)
        .one(db)?
        .unwrap();
    assert_eq!(four.unwrap().id, 44);
    assert_eq!(five.unwrap().id, 55);
    assert_eq!(six.id, 6);

    let (four, five, six) = four::Entity::find_by_id(44)
        .find_also_related(five::Entity)
        .and_also_related(six::Entity)
        .one(db)?
        .unwrap();
    assert_eq!(four.id, 44);
    assert_eq!(five.unwrap().id, 55);
    assert_eq!(six.unwrap().id, 6);

    // below is EntityLoader

    let one_ex = one::Entity::load().one(db)?.unwrap();
    assert_eq!(one_ex.id, 1);

    let one_ex = one::Entity::load().with(two::Entity).one(db)?.unwrap();
    assert_eq!(one_ex.id, 1);
    assert_eq!(one_ex.two.unwrap().id, 2);

    let one_ex = one::Entity::load()
        .with((two::Entity, three::Entity))
        .one(db)?
        .unwrap();
    assert_eq!(one_ex.id, 1);
    let two = one_ex.two.unwrap();
    assert_eq!(two.id, 2);
    assert_eq!(two.three.unwrap().id, 3);

    let one_ex = one::Entity::load()
        .with(six::Entity)
        .with((two::Entity, three::Entity))
        .one(db)?
        .unwrap();
    assert_eq!(one_ex.id, 1);
    let two = one_ex.two.unwrap();
    assert_eq!(two.id, 2);
    assert_eq!(two.three.unwrap().id, 3);
    let six = one_ex.six.unwrap();
    assert_eq!(six.id, 6);

    let one_ex = one::Entity::load()
        .with((six::Entity, five::Entity))
        .with((two::Entity, three::Entity))
        .one(db)?
        .unwrap();
    assert_eq!(one_ex.id, 1);
    let two = one_ex.two.unwrap();
    assert_eq!(two.id, 2);
    assert_eq!(two.three.unwrap().id, 3);
    let six = one_ex.six.unwrap();
    assert_eq!(six.id, 6);
    assert_eq!(six.five.unwrap().id, 55);

    Ok(())
}

#[sea_orm_macros::test]
fn test_delete_by_one_column_unique_keys() -> Result<(), DbErr> {
    let ctx = TestContext::new("test_delete_by_one_column_unique_keys");
    let db = &ctx.db;

    db.get_schema_builder()
        .register(unique_item::Entity)
        .apply(db)?;

    unique_item::ActiveModel {
        id: Set(1),
        name: Set("alpha".to_owned()),
        code: Set(10),
    }
    .insert(db)?;

    unique_item::ActiveModel {
        id: Set(2),
        name: Set("beta".to_owned()),
        code: Set(20),
    }
    .insert(db)?;

    assert_eq!(
        unique_item::Entity::find_by_name("alpha")
            .one(db)?
            .unwrap()
            .id,
        1
    );
    assert_eq!(
        unique_item::Entity::find_by_code(20).one(db)?.unwrap().id,
        2
    );

    assert_eq!(
        unique_item::Entity::delete_by_name("alpha")
            .exec(db)?
            .rows_affected,
        1
    );
    assert!(
        unique_item::Entity::find_by_name("alpha")
            .one(db)?
            .is_none()
    );

    assert_eq!(
        unique_item::Entity::delete_by_code(20)
            .exec(db)?
            .rows_affected,
        1
    );
    assert!(unique_item::Entity::find_by_code(20).one(db)?.is_none());

    Ok(())
}

#[sea_orm_macros::test]
fn test_composite_foreign_key() -> Result<(), DbErr> {
    let ctx = TestContext::new("test_composite_foreign_key");
    let db = &ctx.db;

    #[cfg(not(feature = "schema-sync"))]
    {
        db.get_schema_builder()
            .register(composite_b::Entity)
            .register(composite_a::Entity) // should create a first
            .apply(db)?;
    }

    #[cfg(feature = "schema-sync")]
    {
        // create 1 table only
        db.get_schema_builder()
            .register(composite_a::Entity)
            .apply(db)?;

        // this incrementally creates the 2nd table without index
        if db.get_database_backend() != sea_orm::DbBackend::Sqlite {
            db.get_schema_builder()
                .register(composite_a::Entity)
                .register(composite_b_without_index::Entity)
                .sync(db)?;
        }

        // this creates the full 2nd table
        db.get_schema_builder()
            .register(composite_a::Entity)
            .register(composite_b::Entity)
            .register(composite_c::Entity)
            .sync(db)?;

        // sync again just to be sure
        db.get_schema_builder()
            .register(composite_a::Entity)
            .register(composite_b::Entity)
            .register(composite_c::Entity)
            .sync(db)?;

        #[cfg(feature = "sqlx-postgres")]
        {
            let has_index: bool = db
                .query_one(
                    Query::select()
                        .expr(Expr::cust("COUNT(*) > 0"))
                        .from("pg_indexes")
                        .cond_where(
                            Condition::all()
                                .add(Expr::cust("schemaname = CURRENT_SCHEMA()"))
                                .add(Expr::col("tablename").eq("composite_c"))
                                .add(Expr::col("indexname").eq("idx-composite_c-season_episode")),
                        ),
                )?
                .unwrap()
                .try_get_by_index(0)
                .unwrap();

            assert!(has_index, "Should have index on `composite_c`");
        }
    }

    composite_a::ActiveModel {
        id: Set(100),
        left_id: Set(1),
        right_id: Set(2),
    }
    .insert(db)?;
    composite_a::ActiveModel {
        id: Set(101),
        left_id: Set(1),
        right_id: Set(3),
    }
    .insert(db)?;
    composite_a::ActiveModel {
        id: Set(102),
        left_id: Set(2),
        right_id: Set(3),
    }
    .insert(db)?;
    composite_b::ActiveModel {
        id: Set(200),
        left_id: Set(1),
        right_id: Set(2),
    }
    .insert(db)?;
    composite_b::ActiveModel {
        id: Set(202),
        left_id: Set(2),
        right_id: Set(3),
    }
    .insert(db)?;

    let a = composite_a::Entity::find_by_id(100).one(db)?.unwrap();
    assert_eq!(a.left_id, 1);
    assert_eq!(a.right_id, 2);
    #[rustfmt::skip]
    assert_eq!(composite_a::Entity::find_by_pair((1, 3)).one(db)?.unwrap().id, 101);
    #[rustfmt::skip]
    assert_eq!(composite_a::Entity::load().filter_by_pair((1, 3)).one(db)?.unwrap().id, 101);

    let Some((a, Some(b))) = composite_a::Entity::find_by_id(100)
        .find_also_related(composite_b::Entity)
        .one(db)?
    else {
        panic!("query error")
    };
    assert_eq!(a.left_id, 1);
    assert_eq!(a.right_id, 2);
    assert_eq!(b.id, 200);
    assert_eq!(b.left_id, 1);
    assert_eq!(b.right_id, 2);

    let a = composite_a::Entity::load()
        .filter_by_id(101)
        .with(composite_b::Entity)
        .one(db)?
        .unwrap();

    assert_eq!(a.id, 101);
    assert!(a.b.is_not_found());

    let a = composite_a::Entity::load()
        .filter_by_id(102)
        .with(composite_b::Entity)
        .one(db)?
        .unwrap();

    assert_eq!(a.id, 102);
    assert_eq!(a.left_id, 2);
    assert_eq!(a.right_id, 3);
    assert_eq!(a.b.unwrap().id, 202);

    assert_eq!(
        composite_b::Entity::delete_by_id(202)
            .exec(db)?
            .rows_affected,
        1
    );

    assert_eq!(
        composite_a::Entity::delete_by_pair((2, 3))
            .exec(db)?
            .rows_affected,
        1
    );

    assert!(composite_a::Entity::find_by_id(102).one(db)?.is_none());

    Ok(())
}
