// Copyright (C) 2026 Ordinary Labs, LLC.
//
// SPDX-License-Identifier: AGPL-3.0-only

use ordinary_config::{ModelConfig, StorageLimits, UuidVersion};
use ordinary_storage::{RefDepth, Storage};
use ordinary_types::{Field, Kind};
use saferlmdb::EnvBuilder;
use std::collections::BTreeMap;
use std::{path::Path, sync::Arc};

#[test]
fn integration() -> Result<(), Box<dyn std::error::Error>> {
    let store_dir = Path::new(".ordinary").join("store");

    if std::fs::read_dir(&store_dir).is_ok() {
        std::fs::remove_dir_all(&store_dir)?;
    }

    std::fs::create_dir_all(&store_dir)?;

    let env = Arc::new(unsafe {
        let mut env_builder = EnvBuilder::new()?;
        env_builder.set_maxreaders(126)?;
        env_builder.set_mapsize(16384 * 64 * 10)?;
        env_builder.set_maxdbs(13)?;
        env_builder.open(
            store_dir.to_str().expect("store dir to str"),
            &saferlmdb::open::Flags::empty(),
            0o600,
        )?
    });

    let model_configs = vec![
        ModelConfig {
            idx: 0,
            name: "user".into(),
            fields: vec![
                Field {
                    idx: 1,
                    name: "username".into(),
                    kind: Kind::String,
                    indexed: Some(true),
                    queryable: None,
                    searchable: None,
                    mapping: None,
                    doc: None,
                    encrypted: None,
                    compressed: None,
                },
                Field {
                    idx: 2,
                    name: "chats".into(),
                    kind: Kind::Ref {
                        model: "chat".into(),
                        field: "users".into(),
                        many: Some(true),
                    },
                    indexed: None,
                    queryable: None,
                    searchable: None,
                    mapping: None,
                    doc: None,
                    encrypted: None,
                    compressed: None,
                },
                Field {
                    idx: 3,
                    name: "messages".into(),
                    kind: Kind::Ref {
                        model: "message".into(),
                        field: "user".into(),
                        many: Some(true),
                    },
                    indexed: None,
                    queryable: None,
                    searchable: None,
                    mapping: None,
                    doc: None,
                    encrypted: None,
                    compressed: None,
                },
            ],
            uuid: None,
        },
        ModelConfig {
            idx: 1,
            name: "chat".into(),
            fields: vec![
                Field {
                    idx: 1,
                    name: "name".into(),
                    kind: Kind::String,
                    indexed: None,
                    queryable: None,
                    searchable: None,
                    mapping: None,
                    doc: None,
                    encrypted: None,
                    compressed: None,
                },
                Field {
                    idx: 2,
                    name: "users".into(),
                    kind: Kind::Ref {
                        model: "user".into(),
                        field: "chats".into(),
                        many: Some(true),
                    },
                    indexed: None,
                    queryable: None,
                    searchable: None,
                    mapping: None,
                    doc: None,
                    encrypted: None,
                    compressed: None,
                },
                Field {
                    idx: 3,
                    name: "messages".into(),
                    kind: Kind::Ref {
                        model: "message".into(),
                        field: "chat".into(),
                        many: Some(true),
                    },
                    indexed: None,
                    queryable: None,
                    searchable: None,
                    mapping: None,
                    doc: None,
                    encrypted: None,
                    compressed: None,
                },
            ],
            uuid: Some(UuidVersion::V4),
        },
        ModelConfig {
            idx: 2,
            name: "message".into(),
            fields: vec![
                Field {
                    idx: 1,
                    name: "body".into(),
                    kind: Kind::String,
                    indexed: None,
                    queryable: None,
                    searchable: None,
                    mapping: None,
                    doc: None,
                    encrypted: None,
                    compressed: None,
                },
                Field {
                    idx: 2,
                    name: "user".into(),
                    kind: Kind::Ref {
                        model: "user".into(),
                        field: "messages".into(),
                        many: None,
                    },
                    indexed: None,
                    queryable: None,
                    searchable: None,
                    mapping: None,
                    doc: None,
                    encrypted: None,
                    compressed: None,
                },
                Field {
                    idx: 3,
                    name: "chat".into(),
                    kind: Kind::Ref {
                        model: "chat".into(),
                        field: "messages".into(),
                        many: None,
                    },
                    indexed: None,
                    queryable: None,
                    searchable: None,
                    mapping: None,
                    doc: None,
                    encrypted: None,
                    compressed: None,
                },
            ],
            uuid: Some(UuidVersion::V7),
        },
    ];

    let content_defs = vec![];

    let storage = Storage::new(
        StorageLimits::default(),
        model_configs,
        content_defs,
        [0u8; 32],
        &env,
        store_dir.join("search"),
        false,
    )?;

    let mut user_uuids = vec![];

    for username in ["first_user", "second_user", "third_user"] {
        let mut user_buffer = flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
        let mut user_buffer_vec = user_buffer.start_vector();

        user_buffer_vec.push(username);

        let chats_vec = user_buffer_vec.start_vector();
        chats_vec.end_vector();

        let messages_vec = user_buffer_vec.start_vector();
        messages_vec.end_vector();

        user_buffer_vec.end_vector();

        let uuid = storage.model.insert_model(0, user_buffer.view(), None)?;
        user_uuids.push(uuid);
    }

    let mut chat_uuids = vec![];

    for chat in ["chat_1", "chat_2", "chat_3", "chat_4"] {
        let mut chat_buffer = flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
        let mut chat_buffer_vec = chat_buffer.start_vector();

        chat_buffer_vec.push(chat);

        let mut users_vec = chat_buffer_vec.start_vector();

        match chat {
            "chat_1" => {
                users_vec.push(flexbuffers::Blob(&user_uuids[0][..]));
                users_vec.push(flexbuffers::Blob(&user_uuids[1][..]));
            }
            "chat_2" => {
                users_vec.push(flexbuffers::Blob(&user_uuids[0][..]));
                users_vec.push(flexbuffers::Blob(&user_uuids[2][..]));
            }
            "chat_3" => {
                users_vec.push(flexbuffers::Blob(&user_uuids[1][..]));
                users_vec.push(flexbuffers::Blob(&user_uuids[2][..]));
            }
            "chat_4" => {
                users_vec.push(flexbuffers::Blob(&user_uuids[0][..]));
                users_vec.push(flexbuffers::Blob(&user_uuids[1][..]));
                users_vec.push(flexbuffers::Blob(&user_uuids[2][..]));
            }
            _ => {}
        }

        users_vec.end_vector();

        let messages_vec = chat_buffer_vec.start_vector();
        messages_vec.end_vector();

        chat_buffer_vec.end_vector();

        let uuid = storage.model.insert_model(1, chat_buffer.view(), None)?;
        chat_uuids.push(uuid);
    }

    let mut user_message_uuids = BTreeMap::new();
    let mut chat_message_uuids = BTreeMap::new();

    let mut message_uuids = vec![];

    for message in ["hello", "hi", "sup"] {
        for (i, user_uuid) in user_uuids.iter().enumerate() {
            let curr_chat_uuids = match i {
                0 => vec![chat_uuids[0], chat_uuids[1], chat_uuids[3]],
                1 => vec![chat_uuids[0], chat_uuids[2], chat_uuids[3]],
                2 => vec![chat_uuids[1], chat_uuids[2], chat_uuids[3]],
                _ => vec![],
            };

            for chat_uuid in curr_chat_uuids {
                let mut message_buffer =
                    flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
                let mut message_buffer_vec = message_buffer.start_vector();

                message_buffer_vec.push(message);
                message_buffer_vec.push(flexbuffers::Blob(&user_uuid[..]));
                message_buffer_vec.push(flexbuffers::Blob(&chat_uuid[..]));

                message_buffer_vec.end_vector();

                let uuid = storage.model.insert_model(2, message_buffer.view(), None)?;

                user_message_uuids
                    .entry(user_uuid)
                    .or_insert(vec![])
                    .push(uuid);
                chat_message_uuids
                    .entry(chat_uuid)
                    .or_insert(vec![])
                    .push(uuid);

                message_uuids.push(uuid);
            }
        }
    }

    let user_ref_depth: RefDepth = RefDepth(vec![
        // chats
        (
            (2, 255, None),
            RefDepth(vec![
                // users
                (
                    (2, 255, None),
                    RefDepth(vec![
                        // chats
                        ((2, 0, None), RefDepth(vec![])),
                        // messages
                        ((3, 0, None), RefDepth(vec![])),
                    ]),
                ),
                // messages
                (
                    (3, 255, None),
                    RefDepth(vec![
                        // user
                        (
                            (2, 1, None),
                            RefDepth(vec![
                                // chats
                                ((2, 0, None), RefDepth(vec![])),
                                // messages
                                ((3, 0, None), RefDepth(vec![])),
                            ]),
                        ),
                        // chat
                        (
                            (3, 1, None),
                            RefDepth(vec![
                                // users
                                ((2, 0, None), RefDepth(vec![])),
                                // messages
                                ((3, 0, None), RefDepth(vec![])),
                            ]),
                        ),
                    ]),
                ),
            ]),
        ),
        // messages
        (
            (3, 255, None),
            RefDepth(vec![
                // user
                ((2, 0, None), RefDepth(vec![])),
                // chat
                ((3, 0, None), RefDepth(vec![])),
            ]),
        ),
    ]);

    for user_uuid in &user_uuids {
        let user_item = storage
            .model
            .get_model(0, 0, user_uuid, &user_ref_depth, None)?;
        let user_item_root = flexbuffers::Reader::get_root(&user_item[..])?;

        for (i, user_field) in user_item_root.as_vector().iter().enumerate() {
            match i {
                0 => {
                    // uuid
                    let uuid = user_field.as_blob().0;
                    assert_eq!(&user_uuid[..], uuid);
                }
                1 => {
                    // username
                    println!("user username: {:?}", user_field.as_str());
                }
                2 => {
                    // chats
                    for chat in user_field.as_vector().iter() {
                        for (i, chat_field) in chat.as_vector().iter().enumerate() {
                            match i {
                                0 => {}
                                1 => {
                                    // chat name
                                    println!("user chat name: {:?}", chat_field.as_str());
                                }
                                _ => {}
                            }
                        }
                    }
                }
                3 => {
                    // messages
                    for message in user_field.as_vector().iter() {
                        for (i, message_field) in message.as_vector().iter().enumerate() {
                            match i {
                                0 => {}
                                1 => {
                                    // message body
                                    println!("user message body: {:?}", message_field.as_str());
                                }
                                _ => {}
                            }
                        }
                    }
                }
                _ => {}
            }
        }
    }

    let chats_ref_depth: RefDepth = RefDepth(vec![
        // users
        (
            (2, 255, None),
            RefDepth(vec![
                // chats
                ((2, 0, None), RefDepth(vec![])),
                // messages
                ((3, 0, None), RefDepth(vec![])),
            ]),
        ),
        // messages
        (
            (3, 255, None),
            RefDepth(vec![
                // user
                (
                    (2, 1, None),
                    RefDepth(vec![
                        // chats
                        ((2, 0, None), RefDepth(vec![])),
                        // messages
                        ((3, 0, None), RefDepth(vec![])),
                    ]),
                ),
                // chat
                ((3, 0, None), RefDepth(vec![])),
            ]),
        ),
    ]);

    for chat_uuid in &chat_uuids {
        let chat_item = storage
            .model
            .get_model(1, 0, chat_uuid, &chats_ref_depth, None)?;
        let chat_item_root = flexbuffers::Reader::get_root(&chat_item[..])?;

        for (i, chat_field) in chat_item_root.as_vector().iter().enumerate() {
            match i {
                0 => {
                    // uuid
                    let uuid = chat_field.as_blob().0;
                    assert_eq!(&chat_uuid[..], uuid);
                }
                1 => {
                    // name
                    println!("chat name: {:?}", chat_field.as_str());
                }
                2 => {
                    // users
                    for user in chat_field.as_vector().iter() {
                        for (i, user_field) in user.as_vector().iter().enumerate() {
                            match i {
                                0 => {}
                                1 => {
                                    println!("chat user username: {:?}", user_field.as_str());
                                }
                                _ => {}
                            }
                        }
                    }
                }
                3 => {
                    // messages
                    for message in chat_field.as_vector().iter() {
                        for (i, message_field) in message.as_vector().iter().enumerate() {
                            match i {
                                0 => {}
                                1 => {
                                    println!("chat message body: {:?}", message_field.as_str());
                                }
                                2 => {}
                                3 => {}
                                _ => {}
                            }
                        }
                    }
                }
                _ => {}
            }
        }
    }

    for message_uuid in message_uuids.iter() {
        storage.model.delete_model(2, *message_uuid, None)?;
    }

    for user_uuid in user_uuids.iter() {
        storage.model.delete_model(0, *user_uuid, None)?;
    }

    for chat_uuid in chat_uuids.iter() {
        storage.model.delete_model(1, *chat_uuid, None)?;
    }

    storage.secrets.put("TEST", b"TEST_SECRET")?;
    let secret = storage.secrets.get("TEST")?;

    assert_eq!(secret.as_ref(), b"TEST_SECRET");

    Ok(())
}
