use defguard_common::db::{
    Id,
    models::{Device, polling_token::PollingToken, user::User},
};
use defguard_proto::{
    client_types::{InstanceInfoRequest, InstanceInfoResponse},
    proxy::DeviceInfo,
};
use sqlx::PgPool;
use tonic::Status;

use crate::{enterprise::is_business_license_active, grpc::utils::build_device_config_response};

pub struct PollingServer {
    pool: PgPool,
}

impl PollingServer {
    #[must_use]
    pub fn new(pool: PgPool) -> Self {
        Self { pool }
    }

    /// Checks validity of polling session
    async fn validate_session(&self, token: &str) -> Result<PollingToken<Id>, Status> {
        debug!("Validating polling token. Token: {token}");

        // Polling service is enterprise-only, check the lincense
        if !is_business_license_active() {
            debug!("Instance has enterprise features disabled, denying instance polling info");
            return Err(Status::failed_precondition("no valid license"));
        }

        // Validate the token
        let Some(token) = PollingToken::find(&self.pool, token).await.map_err(|err| {
            error!("Failed to retrieve token: {err}");
            Status::internal("failed to retrieve token")
        })?
        else {
            error!("Invalid token {token:?}");
            return Err(Status::permission_denied("invalid token"));
        };

        // Polling tokens are valid indefinitely
        debug!("Token validation successful {token:?}.");

        Ok(token)
    }

    /// Prepares instance info for polling requests. Enterprise only.
    #[instrument(skip_all)]
    pub async fn info(
        &self,
        request: InstanceInfoRequest,
        device_info: Option<DeviceInfo>,
    ) -> Result<InstanceInfoResponse, Status> {
        trace!("Polling info start");
        let token = self.validate_session(&request.token).await?;
        let Some(device) = Device::find_by_id(&self.pool, token.device_id)
            .await
            .map_err(|err| {
                error!("Failed to retrieve device id {}: {err}", token.device_id);
                Status::internal("failed to retrieve device")
            })?
        else {
            error!("Device id {} not found", token.device_id);
            return Err(Status::internal("device not found"));
        };
        debug!("Polling info for device: {}", device.wireguard_pubkey);

        // Ensure user is active
        let Some(user) = User::find_by_device_id(&self.pool, device.id)
            .await
            .map_err(|err| {
                error!("Failed to retrieve user for device id {}: {err}", device.id);
                Status::internal("failed to retrieve user")
            })?
        else {
            error!("User for device id {} not found", device.id);
            return Err(Status::internal("user not found"));
        };
        if !user.is_active {
            warn!(
                "Denying polling info for inactive user {}({})",
                user.username, user.id
            );
            return Err(Status::permission_denied("user inactive"));
        }

        // Build and return polling info.
        let device_config =
            build_device_config_response(&self.pool, device, None, device_info).await?;

        Ok(InstanceInfoResponse {
            device_config: Some(device_config),
        })
    }
}
