use std::net::IpAddr;

use chrono::{NaiveDateTime, Utc};
use defguard_common::db::{
    Id,
    models::{
        AuthenticationKey, Device, MFAMethod, Settings, User, WebAuthn, WireguardNetwork,
        gateway::Gateway, group::Group, oauth2client::OAuth2Client, proxy::Proxy,
    },
};
use defguard_proto::client_types::MfaMethod;

use crate::{
    db::WebHook,
    enterprise::db::models::{
        activity_log_stream::ActivityLogStream, api_tokens::ApiToken,
        openid_provider::OpenIdProvider, snat::UserSnatBinding,
    },
};

/// Shared context that needs to be added to every API event
///
/// Mainly meant to be stored in the activity log.
/// By design this is a duplicate of a similar struct in the `event_logger` module.
/// This is done in order to avoid circular imports once we split the project into multiple crates.
#[derive(Debug, Clone, PartialEq)]
pub struct ApiRequestContext {
    pub timestamp: NaiveDateTime,
    pub user_id: Id,
    pub username: String,
    pub ip: Option<IpAddr>,
    pub device: String,
}

impl ApiRequestContext {
    #[must_use]
    pub fn new(
        user_id: Id,
        username: String,
        ip: impl Into<Option<IpAddr>>,
        device: String,
    ) -> Self {
        let timestamp = Utc::now().naive_utc();
        Self {
            timestamp,
            user_id,
            username,
            ip: ip.into(),
            device,
        }
    }
}

/// Shared context for every event generated by a user in the gRPC server.
///
/// Similarly to `ApiRequestContexts` at the moment it's mostly meant to populate the activity log.
#[derive(Debug)]
pub struct GrpcRequestContext {
    pub timestamp: NaiveDateTime,
    pub user_id: Id,
    pub username: String,
    pub ip: Option<IpAddr>,
    pub device_id: Id,
    pub device_name: String,
    pub location: WireguardNetwork<Id>,
}

impl GrpcRequestContext {
    #[must_use]
    pub fn new(
        user_id: Id,
        username: String,
        ip: impl Into<Option<IpAddr>>,
        device_id: Id,
        device_name: String,
        location: WireguardNetwork<Id>,
    ) -> Self {
        let timestamp = Utc::now().naive_utc();
        Self {
            timestamp,
            user_id,
            username,
            ip: ip.into(),
            device_id,
            device_name,
            location,
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum ApiEventType {
    UserLogin,
    UserLoginFailed {
        message: String,
    },
    UserLogout,
    UserMfaLogin {
        mfa_method: MFAMethod,
    },
    UserMfaLoginFailed {
        mfa_method: MFAMethod,
        message: String,
    },
    RecoveryCodeLoginFailed,
    RecoveryCodeUsed,
    PasswordChangedByAdmin {
        user: User<Id>,
    },
    PasswordChanged,
    PasswordReset {
        user: User<Id>,
    },
    MfaDisabled,
    UserMfaDisabled {
        user: User<Id>,
    },
    MfaTotpDisabled,
    MfaTotpEnabled,
    MfaEmailDisabled,
    MfaEmailEnabled,
    MfaSecurityKeyAdded {
        key: WebAuthn<Id>,
    },
    MfaSecurityKeyRemoved {
        key: WebAuthn<Id>,
    },
    UserAdded {
        user: User<Id>,
    },
    UserRemoved {
        user: User<Id>,
    },
    UserModified {
        before: User<Id>,
        after: User<Id>,
    },
    UserGroupsModified {
        user: User<Id>,
        before: Vec<String>,
        after: Vec<String>,
    },
    UserDeviceAdded {
        owner: User<Id>,
        device: Device<Id>,
    },
    UserDeviceRemoved {
        owner: User<Id>,
        device: Device<Id>,
    },
    UserDeviceModified {
        owner: User<Id>,
        before: Device<Id>,
        after: Device<Id>,
    },
    NetworkDeviceAdded {
        device: Device<Id>,
        location: WireguardNetwork<Id>,
    },
    NetworkDeviceRemoved {
        device: Device<Id>,
        location: WireguardNetwork<Id>,
    },
    NetworkDeviceModified {
        before: Device<Id>,
        after: Device<Id>,
        location: WireguardNetwork<Id>,
    },
    ActivityLogStreamCreated {
        stream: ActivityLogStream<Id>,
    },
    ActivityLogStreamModified {
        before: ActivityLogStream<Id>,
        after: ActivityLogStream<Id>,
    },
    ActivityLogStreamRemoved {
        stream: ActivityLogStream<Id>,
    },
    VpnLocationAdded {
        location: WireguardNetwork<Id>,
    },
    VpnLocationRemoved {
        location: WireguardNetwork<Id>,
    },
    VpnLocationModified {
        before: WireguardNetwork<Id>,
        after: WireguardNetwork<Id>,
    },
    ApiTokenAdded {
        owner: User<Id>,
        token: ApiToken<Id>,
    },
    ApiTokenRemoved {
        owner: User<Id>,
        token: ApiToken<Id>,
    },
    ApiTokenRenamed {
        owner: User<Id>,
        token: ApiToken<Id>,
        old_name: String,
        new_name: String,
    },
    OpenIdAppAdded {
        app: OAuth2Client<Id>,
    },
    OpenIdAppRemoved {
        app: OAuth2Client<Id>,
    },
    OpenIdAppModified {
        before: OAuth2Client<Id>,
        after: OAuth2Client<Id>,
    },
    OpenIdAppStateChanged {
        app: OAuth2Client<Id>,
        enabled: bool,
    },
    OpenIdProviderModified {
        provider: OpenIdProvider<Id>,
    },
    OpenIdProviderRemoved {
        provider: OpenIdProvider<Id>,
    },
    SettingsUpdated {
        before: Settings,
        after: Settings,
    },
    SettingsUpdatedPartial {
        before: Settings,
        after: Settings,
    },
    SettingsDefaultBrandingRestored,
    GroupsBulkAssigned {
        users: Vec<User<Id>>,
        groups: Vec<Group<Id>>,
    },
    GroupAdded {
        group: Group<Id>,
    },
    GroupModified {
        before: Group<Id>,
        after: Group<Id>,
    },
    GroupRemoved {
        group: Group<Id>,
    },
    GroupMemberAdded {
        group: Group<Id>,
        user: User<Id>,
    },
    GroupMemberRemoved {
        group: Group<Id>,
        user: User<Id>,
    },
    GroupMembersModified {
        group: Group<Id>,
        added: Vec<User<Id>>,
        removed: Vec<User<Id>>,
    },
    WebHookAdded {
        webhook: WebHook<Id>,
    },
    WebHookModified {
        before: WebHook<Id>,
        after: WebHook<Id>,
    },
    WebHookRemoved {
        webhook: WebHook<Id>,
    },
    WebHookStateChanged {
        webhook: WebHook<Id>,
        enabled: bool,
    },
    AuthenticationKeyAdded {
        key: AuthenticationKey<Id>,
    },
    AuthenticationKeyRemoved {
        key: AuthenticationKey<Id>,
    },
    AuthenticationKeyRenamed {
        key: AuthenticationKey<Id>,
        old_name: Option<String>,
        new_name: Option<String>,
    },
    EnrollmentTokenAdded {
        user: User<Id>,
    },
    ClientConfigurationTokenAdded {
        user: User<Id>,
    },
    UserSnatBindingAdded {
        user: User<Id>,
        location: WireguardNetwork<Id>,
        binding: UserSnatBinding<Id>,
    },
    UserSnatBindingRemoved {
        user: User<Id>,
        location: WireguardNetwork<Id>,
        binding: UserSnatBinding<Id>,
    },
    UserSnatBindingModified {
        user: User<Id>,
        location: WireguardNetwork<Id>,
        before: UserSnatBinding<Id>,
        after: UserSnatBinding<Id>,
    },
    ProxyModified {
        before: Proxy<Id>,
        after: Proxy<Id>,
    },
    ProxyDeleted {
        proxy: Proxy<Id>,
    },
    GatewayModified {
        before: Gateway<Id>,
        after: Gateway<Id>,
    },
    GatewayDeleted {
        gateway: Gateway<Id>,
    },
}

/// Events from Web API
#[derive(Debug)]
pub struct ApiEvent {
    pub context: ApiRequestContext,
    pub event: Box<ApiEventType>,
}

/// Shared context for every event generated from a user request in the bi-directional gRPC stream.
///
/// Similarly to `ApiRequestContexts` at the moment it's mostly meant to populate the activity log.
#[derive(Debug)]
pub struct BidiRequestContext {
    pub timestamp: NaiveDateTime,
    pub user_id: Id,
    pub username: String,
    pub ip: Option<IpAddr>,
    pub device_name: String,
}

impl BidiRequestContext {
    #[must_use]
    pub fn new(
        user_id: Id,
        username: String,
        ip: impl Into<Option<IpAddr>>,
        device_name: String,
    ) -> Self {
        let timestamp = Utc::now().naive_utc();
        Self {
            timestamp,
            user_id,
            username,
            ip: ip.into(),
            device_name,
        }
    }
}

/// Events emmited from gRPC bi-directional communication stream
#[derive(Debug)]
pub struct BidiStreamEvent {
    pub context: BidiRequestContext,
    pub event: BidiStreamEventType,
}

/// Wrapper enum for different types of events emitted by the bidi stream.
///
/// Each variant represents a separate gRPC service that's part of the bi-directional communications server.
#[derive(Debug)]
pub enum BidiStreamEventType {
    Enrollment(Box<EnrollmentEvent>),
    PasswordReset(Box<PasswordResetEvent>),
    DesktopClientMfa(Box<DesktopClientMfaEvent>),
}

#[derive(Debug)]
pub enum EnrollmentEvent {
    EnrollmentStarted,
    EnrollmentDeviceAdded { device: Device<Id> },
    EnrollmentCompleted,
}

#[derive(Debug)]
pub enum PasswordResetEvent {
    PasswordResetRequested,
    PasswordResetStarted,
    PasswordResetCompleted,
}

pub type ClientMFAMethod = MfaMethod;

#[derive(Debug)]
pub enum DesktopClientMfaEvent {
    Success {
        device: Device<Id>,
        location: WireguardNetwork<Id>,
        method: ClientMFAMethod,
    },
    Failed {
        device: Device<Id>,
        location: WireguardNetwork<Id>,
        method: ClientMFAMethod,
        message: String,
    },
    Disconnected {
        device: Device<Id>,
        location: WireguardNetwork<Id>,
        is_mfa_session: bool,
    },
}
