use std::net::IpAddr;

use defguard_common::db::{Id, NoId};
use model_derive::Model;
use serde::{Deserialize, Serialize};
use sqlx::{PgExecutor, query_as};
use utoipa::ToSchema;

use crate::enterprise::snat::error::UserSnatBindingError;

#[derive(Clone, Debug, Deserialize, Model, Serialize, ToSchema, PartialEq)]
#[table(user_snat_binding)]
pub struct UserSnatBinding<I = NoId> {
    pub id: I,
    pub user_id: Id,
    pub location_id: Id,
    #[model(ip)]
    #[schema(value_type = String)]
    pub public_ip: IpAddr,
}

impl UserSnatBinding {
    #[must_use]
    pub fn new(user_id: Id, location_id: Id, public_ip: IpAddr) -> Self {
        Self {
            id: NoId,
            user_id,
            location_id,
            public_ip,
        }
    }
}

impl UserSnatBinding<Id> {
    pub(crate) async fn find_binding<'e, E>(
        executor: E,
        location_id: Id,
        user_id: Id,
    ) -> Result<Self, UserSnatBindingError>
    where
        E: PgExecutor<'e>,
    {
        let binding = query_as!(
            Self,
            "SELECT id, user_id, location_id, \"public_ip\" \"public_ip: IpAddr\" \
			FROM user_snat_binding WHERE location_id = $1 AND user_id = $2",
            location_id,
            user_id
        )
        .fetch_one(executor)
        .await?;

        Ok(binding)
    }

    pub async fn all_for_location<'e, E>(executor: E, location_id: Id) -> sqlx::Result<Vec<Self>>
    where
        E: PgExecutor<'e>,
    {
        let bindings = query_as!(
            Self,
            "SELECT id, user_id, location_id, \"public_ip\" \"public_ip: IpAddr\" \
			FROM user_snat_binding WHERE location_id = $1",
            location_id
        )
        .fetch_all(executor)
        .await?;

        Ok(bindings)
    }
}
