use std::{
    collections::{BTreeMap, HashMap},
    sync::{Arc, Mutex as StdMutex},
};

use lru_cache::LruCache;
use tokio::sync::{broadcast, Mutex, RwLock};

use crate::{observability::FilterReloadHandles, Config, Result};

pub(crate) mod account_data;
pub(crate) mod admin;
pub(crate) mod appservice;
pub(crate) mod globals;
pub(crate) mod key_backups;
pub(crate) mod media;
pub(crate) mod pdu;
pub(crate) mod pusher;
pub(crate) mod rooms;
pub(crate) mod sending;
pub(crate) mod transaction_ids;
pub(crate) mod uiaa;
pub(crate) mod users;

pub(crate) struct Services {
    pub(crate) appservice: appservice::Service,
    pub(crate) pusher: pusher::Service,
    pub(crate) rooms: rooms::Service,
    pub(crate) transaction_ids: transaction_ids::Service,
    pub(crate) uiaa: uiaa::Service,
    pub(crate) users: users::Service,
    pub(crate) account_data: account_data::Service,
    pub(crate) admin: Arc<admin::Service>,
    pub(crate) globals: globals::Service,
    pub(crate) key_backups: key_backups::Service,
    pub(crate) media: media::Service,
    pub(crate) sending: Arc<sending::Service>,
}

impl Services {
    pub(crate) fn build<
        D: appservice::Data
            + pusher::Data
            + rooms::Data
            + transaction_ids::Data
            + uiaa::Data
            + users::Data
            + account_data::Data
            + globals::Data
            + key_backups::Data
            + media::Data
            + sending::Data
            + 'static,
    >(
        db: &'static D,
        config: Config,
        reload_handles: FilterReloadHandles,
    ) -> Result<Self> {
        Ok(Self {
            appservice: appservice::Service::build(db)?,
            pusher: pusher::Service {
                db,
            },
            rooms: rooms::Service {
                alias: rooms::alias::Service::new(db),
                auth_chain: rooms::auth_chain::Service {
                    db,
                },
                directory: db,
                edus: rooms::edus::Service {
                    read_receipt: db,
                    typing: rooms::edus::typing::Service {
                        typing: RwLock::new(BTreeMap::new()),
                        last_typing_update: RwLock::new(BTreeMap::new()),
                        typing_update_sender: broadcast::channel(100).0,
                    },
                },
                event_handler: rooms::event_handler::Service,
                lazy_loading: rooms::lazy_loading::Service {
                    db,
                    lazy_load_waiting: Mutex::new(HashMap::new()),
                },
                metadata: db,
                outlier: db,
                pdu_metadata: rooms::pdu_metadata::Service {
                    db,
                },
                search: db,
                short: db,
                state: rooms::state::Service {
                    db,
                },
                state_accessor: rooms::state_accessor::Service {
                    db,
                    #[allow(
                        clippy::as_conversions,
                        clippy::cast_sign_loss,
                        clippy::cast_possible_truncation
                    )]
                    server_visibility_cache: StdMutex::new(LruCache::new(
                        (100.0 * config.cache_capacity_modifier) as usize,
                    )),
                    #[allow(
                        clippy::as_conversions,
                        clippy::cast_sign_loss,
                        clippy::cast_possible_truncation
                    )]
                    user_visibility_cache: StdMutex::new(LruCache::new(
                        (100.0 * config.cache_capacity_modifier) as usize,
                    )),
                },
                state_cache: rooms::state_cache::Service {
                    db,
                },
                state_compressor: rooms::state_compressor::Service {
                    db,
                    #[allow(
                        clippy::as_conversions,
                        clippy::cast_sign_loss,
                        clippy::cast_possible_truncation
                    )]
                    stateinfo_cache: StdMutex::new(LruCache::new(
                        (100.0 * config.cache_capacity_modifier) as usize,
                    )),
                },
                timeline: rooms::timeline::Service {
                    db,
                },
                threads: rooms::threads::Service {
                    db,
                },
                spaces: rooms::spaces::Service {
                    roomid_spacechunk_cache: Mutex::new(LruCache::new(200)),
                },
                user: db,
            },
            transaction_ids: db,
            uiaa: uiaa::Service {
                db,
            },
            users: users::Service {
                db,
                connections: StdMutex::new(BTreeMap::new()),
            },
            account_data: db,
            admin: admin::Service::build(),
            key_backups: db,
            media: media::Service::new(db),
            sending: sending::Service::build(db, &config),

            globals: globals::Service::load(db, config, reload_handles)?,
        })
    }

    async fn memory_usage(&self) -> String {
        let lazy_load_waiting =
            self.rooms.lazy_loading.lazy_load_waiting.lock().await.len();
        let server_visibility_cache = self
            .rooms
            .state_accessor
            .server_visibility_cache
            .lock()
            .unwrap()
            .len();
        let user_visibility_cache = self
            .rooms
            .state_accessor
            .user_visibility_cache
            .lock()
            .unwrap()
            .len();
        let stateinfo_cache =
            self.rooms.state_compressor.stateinfo_cache.lock().unwrap().len();
        let roomid_spacechunk_cache =
            self.rooms.spaces.roomid_spacechunk_cache.lock().await.len();

        format!(
            "\
lazy_load_waiting: {lazy_load_waiting}
server_visibility_cache: {server_visibility_cache}
user_visibility_cache: {user_visibility_cache}
stateinfo_cache: {stateinfo_cache}
roomid_spacechunk_cache: {roomid_spacechunk_cache}"
        )
    }

    async fn clear_caches(&self, amount: u32) {
        if amount > 0 {
            self.rooms.lazy_loading.lazy_load_waiting.lock().await.clear();
        }
        if amount > 1 {
            self.rooms
                .state_accessor
                .server_visibility_cache
                .lock()
                .unwrap()
                .clear();
        }
        if amount > 2 {
            self.rooms
                .state_accessor
                .user_visibility_cache
                .lock()
                .unwrap()
                .clear();
        }
        if amount > 3 {
            self.rooms.state_compressor.stateinfo_cache.lock().unwrap().clear();
        }
        if amount > 5 {
            self.rooms.spaces.roomid_spacechunk_cache.lock().await.clear();
        }
    }
}
