// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

use crate::permissioner::Permissioner;
use crate::stm::StateHandler;
use crate::stm::snapshot::Snapshotable;
use crate::{collect_handlers, define_state, impl_fill_restore};
use ahash::AHashMap;
use bytes::Bytes;
use iggy_binary_protocol::WireIdentifier;
use iggy_binary_protocol::requests::personal_access_tokens::{
    CreatePersonalAccessTokenRequest, DeletePersonalAccessTokenRequest,
};
use iggy_binary_protocol::requests::users::{
    ChangePasswordRequest, CreateUserRequest, DeleteUserRequest, UpdatePermissionsRequest,
    UpdateUserRequest,
};
use iggy_common::{
    GlobalPermissions, IggyExpiry, IggyTimestamp, Permissions, PersonalAccessToken,
    StreamPermissions, UserId, UserStatus,
};
use serde::{Deserialize, Serialize};
use slab::Slab;
use std::sync::Arc;

// ============================================================================
// User Entity
// ============================================================================

#[derive(Debug, Clone)]
pub struct User {
    pub id: UserId,
    pub username: Arc<str>,
    pub password_hash: Arc<str>,
    pub status: UserStatus,
    pub created_at: IggyTimestamp,
    pub permissions: Option<Arc<Permissions>>,
}

impl Default for User {
    fn default() -> Self {
        Self {
            id: 0,
            username: Arc::from(""),
            password_hash: Arc::from(""),
            status: UserStatus::default(),
            created_at: IggyTimestamp::default(),
            permissions: None,
        }
    }
}

impl User {
    #[must_use]
    pub const fn new(
        username: Arc<str>,
        password_hash: Arc<str>,
        status: UserStatus,
        created_at: IggyTimestamp,
        permissions: Option<Arc<Permissions>>,
    ) -> Self {
        Self {
            id: 0,
            username,
            password_hash,
            status,
            created_at,
            permissions,
        }
    }
}

define_state! {
    Users {
        index: AHashMap<Arc<str>, UserId>,
        items: Slab<User>,
        personal_access_tokens: AHashMap<UserId, AHashMap<Arc<str>, PersonalAccessToken>>,
        permissioner: Permissioner,
    }
}

collect_handlers! {
    Users {
        CreateUser,
        UpdateUser,
        DeleteUser,
        ChangePassword,
        UpdatePermissions,
        CreatePersonalAccessToken,
        DeletePersonalAccessToken,
    }
}

impl UsersInner {
    fn resolve_user_id(&self, identifier: &WireIdentifier) -> Option<usize> {
        match identifier {
            WireIdentifier::Numeric(id) => {
                let id = *id as usize;
                if self.items.contains(id) {
                    Some(id)
                } else {
                    None
                }
            }
            WireIdentifier::String(name) => self.index.get(name.as_str()).map(|&id| id as usize),
        }
    }
}

// TODO(hubcio): Serialize proper reply (e.g. assigned user ID) instead of empty Bytes.
impl StateHandler for CreateUserRequest {
    type State = UsersInner;
    #[allow(clippy::cast_possible_truncation)]
    fn apply(&self, state: &mut UsersInner) -> Bytes {
        let username_arc: Arc<str> = Arc::from(self.username.as_str());
        if state.index.contains_key(&username_arc) {
            return Bytes::new();
        }

        let status = UserStatus::from_code(self.status).unwrap_or_default();
        let permissions = self
            .permissions
            .as_ref()
            .map(|p| Arc::new(Permissions::from(p.clone())));

        let user = User {
            id: 0,
            username: username_arc.clone(),
            password_hash: Arc::from(self.password.as_str()),
            status,
            created_at: IggyTimestamp::now(),
            permissions,
        };

        let id = state.items.insert(user);
        if let Some(user) = state.items.get_mut(id) {
            user.id = id as UserId;
        }

        state.index.insert(username_arc, id as UserId);
        state
            .personal_access_tokens
            .insert(id as UserId, AHashMap::default());
        Bytes::new()
    }
}

impl StateHandler for UpdateUserRequest {
    type State = UsersInner;
    #[allow(clippy::cast_possible_truncation)]
    fn apply(&self, state: &mut UsersInner) -> Bytes {
        let Some(user_id) = state.resolve_user_id(&self.user_id) else {
            return Bytes::new();
        };

        let Some(user) = state.items.get_mut(user_id) else {
            return Bytes::new();
        };

        if let Some(new_username) = &self.username {
            let new_username_arc: Arc<str> = Arc::from(new_username.as_str());
            if let Some(&existing_id) = state.index.get(&new_username_arc)
                && existing_id != user_id as UserId
            {
                return Bytes::new();
            }

            state.index.remove(&user.username);
            user.username = new_username_arc.clone();
            state.index.insert(new_username_arc, user_id as UserId);
        }

        if let Some(status_code) = self.status
            && let Ok(new_status) = UserStatus::from_code(status_code)
        {
            user.status = new_status;
        }
        Bytes::new()
    }
}

impl StateHandler for DeleteUserRequest {
    type State = UsersInner;
    #[allow(clippy::cast_possible_truncation)]
    fn apply(&self, state: &mut UsersInner) -> Bytes {
        let Some(user_id) = state.resolve_user_id(&self.user_id) else {
            return Bytes::new();
        };

        if let Some(user) = state.items.get(user_id) {
            let username = user.username.clone();
            state.items.remove(user_id);
            state.index.remove(&username);
            state.personal_access_tokens.remove(&(user_id as UserId));
        }
        Bytes::new()
    }
}

impl StateHandler for ChangePasswordRequest {
    type State = UsersInner;
    fn apply(&self, state: &mut UsersInner) -> Bytes {
        let Some(user_id) = state.resolve_user_id(&self.user_id) else {
            return Bytes::new();
        };

        if let Some(user) = state.items.get_mut(user_id) {
            user.password_hash = Arc::from(self.new_password.as_str());
        }
        Bytes::new()
    }
}

impl StateHandler for UpdatePermissionsRequest {
    type State = UsersInner;
    fn apply(&self, state: &mut UsersInner) -> Bytes {
        let Some(user_id) = state.resolve_user_id(&self.user_id) else {
            return Bytes::new();
        };

        if let Some(user) = state.items.get_mut(user_id) {
            user.permissions = self
                .permissions
                .as_ref()
                .map(|p| Arc::new(Permissions::from(p.clone())));
        }
        Bytes::new()
    }
}

// TODO(hubcio): Serialize proper reply (e.g. generated token) instead of empty Bytes.
impl StateHandler for CreatePersonalAccessTokenRequest {
    type State = UsersInner;
    fn apply(&self, state: &mut UsersInner) -> Bytes {
        // TODO: Stub until protocol gets adjusted.
        let user_id = 0;
        let expiry = IggyExpiry::from(self.expiry);
        let user_tokens = state.personal_access_tokens.entry(user_id).or_default();
        let name_arc: Arc<str> = Arc::from(self.name.as_str());
        if user_tokens.contains_key(&name_arc) {
            return Bytes::new();
        }

        let expiry_at = PersonalAccessToken::calculate_expiry_at(IggyTimestamp::now(), expiry);
        if let Some(expiry_at) = expiry_at
            && expiry_at.as_micros() <= IggyTimestamp::now().as_micros()
        {
            return Bytes::new();
        }

        let (pat, _) =
            PersonalAccessToken::new(user_id, self.name.as_ref(), IggyTimestamp::now(), expiry);
        user_tokens.insert(name_arc, pat);
        Bytes::new()
    }
}

impl StateHandler for DeletePersonalAccessTokenRequest {
    type State = UsersInner;
    fn apply(&self, state: &mut UsersInner) -> Bytes {
        // TODO: Stub until protocol gets adjusted.
        let user_id = 0;

        if let Some(user_tokens) = state.personal_access_tokens.get_mut(&user_id) {
            let name_arc: Arc<str> = Arc::from(self.name.as_str());
            user_tokens.remove(&name_arc);
        }
        Bytes::new()
    }
}

/// User snapshot representation for serialization.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserSnapshot {
    pub id: UserId,
    pub username: String,
    pub password_hash: String,
    pub status: UserStatus,
    pub created_at: IggyTimestamp,
    pub permissions: Option<Permissions>,
}

/// Personal access token snapshot representation for serialization.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersonalAccessTokenSnapshot {
    pub user_id: UserId,
    pub name: String,
    pub token: String,
    pub expiry_at: Option<IggyTimestamp>,
}

/// Permissioner snapshot representation for serialization.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionerSnapshot {
    pub users_permissions: Vec<(UserId, GlobalPermissions)>,
    pub users_streams_permissions: Vec<((UserId, usize), StreamPermissions)>,
    pub users_that_can_poll_messages_from_all_streams: Vec<UserId>,
    pub users_that_can_send_messages_to_all_streams: Vec<UserId>,
    pub users_that_can_poll_messages_from_specific_streams: Vec<(UserId, usize)>,
    pub users_that_can_send_messages_to_specific_streams: Vec<(UserId, usize)>,
}

/// Snapshot representation for the Users state machine.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsersSnapshot {
    pub items: Vec<(usize, UserSnapshot)>,
    pub personal_access_tokens: Vec<(UserId, Vec<(String, PersonalAccessTokenSnapshot)>)>,
    pub permissioner: PermissionerSnapshot,
}

impl Snapshotable for Users {
    type Snapshot = UsersSnapshot;

    fn to_snapshot(&self) -> Self::Snapshot {
        self.inner.read(|inner| {
            let items: Vec<(usize, UserSnapshot)> = inner
                .items
                .iter()
                .map(|(user_id, user)| {
                    (
                        user_id,
                        UserSnapshot {
                            id: user.id,
                            username: user.username.to_string(),
                            password_hash: user.password_hash.to_string(),
                            status: user.status,
                            created_at: user.created_at,
                            permissions: user.permissions.as_ref().map(|p| (**p).clone()),
                        },
                    )
                })
                .collect();

            let personal_access_tokens: Vec<(UserId, Vec<(String, PersonalAccessTokenSnapshot)>)> =
                inner
                    .personal_access_tokens
                    .iter()
                    .map(|(&user_id, tokens)| {
                        let token_list: Vec<(String, PersonalAccessTokenSnapshot)> = tokens
                            .iter()
                            .map(|(name, pat)| {
                                (
                                    name.to_string(),
                                    PersonalAccessTokenSnapshot {
                                        user_id: pat.user_id,
                                        name: pat.name.to_string(),
                                        token: pat.token.to_string(),
                                        expiry_at: pat.expiry_at,
                                    },
                                )
                            })
                            .collect();
                        (user_id, token_list)
                    })
                    .collect();

            let permissioner = PermissionerSnapshot {
                users_permissions: inner
                    .permissioner
                    .users_permissions
                    .iter()
                    .map(|(&k, v)| (k, v.clone()))
                    .collect(),
                users_streams_permissions: inner
                    .permissioner
                    .users_streams_permissions
                    .iter()
                    .map(|(&k, v)| (k, v.clone()))
                    .collect(),
                users_that_can_poll_messages_from_all_streams: inner
                    .permissioner
                    .users_that_can_poll_messages_from_all_streams
                    .iter()
                    .copied()
                    .collect(),
                users_that_can_send_messages_to_all_streams: inner
                    .permissioner
                    .users_that_can_send_messages_to_all_streams
                    .iter()
                    .copied()
                    .collect(),
                users_that_can_poll_messages_from_specific_streams: inner
                    .permissioner
                    .users_that_can_poll_messages_from_specific_streams
                    .iter()
                    .copied()
                    .collect(),
                users_that_can_send_messages_to_specific_streams: inner
                    .permissioner
                    .users_that_can_send_messages_to_specific_streams
                    .iter()
                    .copied()
                    .collect(),
            };

            UsersSnapshot {
                items,
                personal_access_tokens,
                permissioner,
            }
        })
    }

    #[allow(clippy::cast_possible_truncation)]
    fn from_snapshot(
        snapshot: Self::Snapshot,
    ) -> Result<Self, crate::stm::snapshot::SnapshotError> {
        let mut index: AHashMap<Arc<str>, UserId> = AHashMap::new();
        let mut user_entries: Vec<(usize, User)> = Vec::new();

        for (slab_key, user_snap) in snapshot.items {
            let username: Arc<str> = Arc::from(user_snap.username.as_str());
            let user = User {
                id: user_snap.id,
                username: username.clone(),
                password_hash: Arc::from(user_snap.password_hash.as_str()),
                status: user_snap.status,
                created_at: user_snap.created_at,
                permissions: user_snap.permissions.map(Arc::new),
            };

            index.insert(username, slab_key as UserId);
            user_entries.push((slab_key, user));
        }

        let items: Slab<User> = user_entries.into_iter().collect();

        let mut personal_access_tokens: AHashMap<UserId, AHashMap<Arc<str>, PersonalAccessToken>> =
            AHashMap::new();
        for (user_id, tokens) in snapshot.personal_access_tokens {
            let mut token_map: AHashMap<Arc<str>, PersonalAccessToken> = AHashMap::new();
            for (name, pat_snap) in tokens {
                let pat = PersonalAccessToken::raw(
                    pat_snap.user_id,
                    &pat_snap.name,
                    &pat_snap.token,
                    pat_snap.expiry_at,
                );
                token_map.insert(Arc::from(name.as_str()), pat);
            }
            personal_access_tokens.insert(user_id, token_map);
        }

        let permissioner = Permissioner {
            users_permissions: snapshot
                .permissioner
                .users_permissions
                .into_iter()
                .collect(),
            users_streams_permissions: snapshot
                .permissioner
                .users_streams_permissions
                .into_iter()
                .collect(),
            users_that_can_poll_messages_from_all_streams: snapshot
                .permissioner
                .users_that_can_poll_messages_from_all_streams
                .into_iter()
                .collect(),
            users_that_can_send_messages_to_all_streams: snapshot
                .permissioner
                .users_that_can_send_messages_to_all_streams
                .into_iter()
                .collect(),
            users_that_can_poll_messages_from_specific_streams: snapshot
                .permissioner
                .users_that_can_poll_messages_from_specific_streams
                .into_iter()
                .collect(),
            users_that_can_send_messages_to_specific_streams: snapshot
                .permissioner
                .users_that_can_send_messages_to_specific_streams
                .into_iter()
                .collect(),
        };

        let inner = UsersInner {
            index,
            items,
            personal_access_tokens,
            permissioner,
            last_result: None,
        };
        Ok(inner.into())
    }
}

impl_fill_restore!(Users, users);
