use diesel_models::authorization::AuthorizationUpdateInternal;
use error_stack::report;
use router_env::{instrument, tracing};

use super::{MockDb, Store};
use crate::{
    connection,
    core::errors::{self, CustomResult},
    types::storage,
};

#[async_trait::async_trait]
pub trait AuthorizationInterface {
    async fn insert_authorization(
        &self,
        authorization: storage::AuthorizationNew,
    ) -> CustomResult<storage::Authorization, errors::StorageError>;

    async fn find_all_authorizations_by_processor_merchant_id_payment_id(
        &self,
        processor_merchant_id: &common_utils::id_type::MerchantId,
        payment_id: &common_utils::id_type::PaymentId,
    ) -> CustomResult<Vec<storage::Authorization>, errors::StorageError>;

    async fn update_authorization_by_processor_merchant_id_authorization_id(
        &self,
        processor_merchant_id: common_utils::id_type::MerchantId,
        authorization_id: String,
        authorization: storage::AuthorizationUpdate,
    ) -> CustomResult<storage::Authorization, errors::StorageError>;
}

#[async_trait::async_trait]
impl AuthorizationInterface for Store {
    #[instrument(skip_all)]
    async fn insert_authorization(
        &self,
        authorization: storage::AuthorizationNew,
    ) -> CustomResult<storage::Authorization, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        authorization
            .insert(&conn)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn find_all_authorizations_by_processor_merchant_id_payment_id(
        &self,
        processor_merchant_id: &common_utils::id_type::MerchantId,
        payment_id: &common_utils::id_type::PaymentId,
    ) -> CustomResult<Vec<storage::Authorization>, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        // Stagger release fallback: first try processor_merchant_id, if empty fallback to merchant_id
        // For old records processor_merchant_id is NULL, so we use merchant_id (which has the same value)
        let authorizations = storage::Authorization::find_by_processor_merchant_id_payment_id(
            &conn,
            processor_merchant_id,
            payment_id,
        )
        .await
        .map_err(|error| report!(errors::StorageError::from(error)))?;

        if authorizations.is_empty() {
            storage::Authorization::find_by_merchant_id_payment_id(
                &conn,
                processor_merchant_id,
                payment_id,
            )
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
        } else {
            Ok(authorizations)
        }
    }

    #[instrument(skip_all)]
    async fn update_authorization_by_processor_merchant_id_authorization_id(
        &self,
        processor_merchant_id: common_utils::id_type::MerchantId,
        authorization_id: String,
        authorization: storage::AuthorizationUpdate,
    ) -> CustomResult<storage::Authorization, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        // Stagger release fallback: first try processor_merchant_id, if not found fallback to merchant_id
        // For old records processor_merchant_id is NULL, so we use merchant_id (which has the same value)
        let result = storage::Authorization::update_by_processor_merchant_id_authorization_id(
            &conn,
            processor_merchant_id.clone(),
            authorization_id.clone(),
            authorization.clone(),
        )
        .await;

        match result {
            Ok(auth) => Ok(auth),
            Err(error) => {
                if matches!(
                    error.current_context(),
                    diesel_models::errors::DatabaseError::NotFound
                ) {
                    storage::Authorization::update_by_merchant_id_authorization_id(
                        &conn,
                        processor_merchant_id,
                        authorization_id,
                        authorization,
                    )
                    .await
                    .map_err(|error| report!(errors::StorageError::from(error)))
                } else {
                    Err(report!(errors::StorageError::from(error)))
                }
            }
        }
    }
}

#[async_trait::async_trait]
impl AuthorizationInterface for MockDb {
    async fn insert_authorization(
        &self,
        authorization: storage::AuthorizationNew,
    ) -> CustomResult<storage::Authorization, errors::StorageError> {
        let mut authorizations = self.authorizations.lock().await;
        if authorizations.iter().any(|authorization_inner| {
            authorization_inner.authorization_id == authorization.authorization_id
        }) {
            Err(errors::StorageError::DuplicateValue {
                entity: "authorization_id",
                key: None,
            })?
        }
        let authorization = storage::Authorization {
            authorization_id: authorization.authorization_id,
            merchant_id: authorization.merchant_id,
            payment_id: authorization.payment_id,
            amount: authorization.amount,
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            status: authorization.status,
            error_code: authorization.error_code,
            error_message: authorization.error_message,
            connector_authorization_id: authorization.connector_authorization_id,
            previously_authorized_amount: authorization.previously_authorized_amount,
            processor_merchant_id: authorization.processor_merchant_id,
        };
        authorizations.push(authorization.clone());
        Ok(authorization)
    }

    async fn find_all_authorizations_by_processor_merchant_id_payment_id(
        &self,
        processor_merchant_id: &common_utils::id_type::MerchantId,
        payment_id: &common_utils::id_type::PaymentId,
    ) -> CustomResult<Vec<storage::Authorization>, errors::StorageError> {
        let authorizations = self.authorizations.lock().await;
        // Stagger release fallback: first try processor_merchant_id, if empty fallback to merchant_id
        // For old records processor_merchant_id is NULL, so we use merchant_id (which has the same value)
        let authorizations_found: Vec<storage::Authorization> = authorizations
            .iter()
            .filter(|a| {
                a.processor_merchant_id.as_ref() == Some(processor_merchant_id)
                    && a.payment_id == *payment_id
            })
            .cloned()
            .collect();

        if authorizations_found.is_empty() {
            Ok(authorizations
                .iter()
                .filter(|a| a.merchant_id == *processor_merchant_id && a.payment_id == *payment_id)
                .cloned()
                .collect())
        } else {
            Ok(authorizations_found)
        }
    }

    async fn update_authorization_by_processor_merchant_id_authorization_id(
        &self,
        processor_merchant_id: common_utils::id_type::MerchantId,
        authorization_id: String,
        authorization_update: storage::AuthorizationUpdate,
    ) -> CustomResult<storage::Authorization, errors::StorageError> {
        let mut authorizations = self.authorizations.lock().await;
        // Stagger release fallback: first try processor_merchant_id, if not found fallback to merchant_id
        // For old records processor_merchant_id is NULL, so we use merchant_id (which has the same value)
        let index = authorizations
            .iter()
            .position(|authorization| {
                authorization.authorization_id == authorization_id
                    && authorization.processor_merchant_id.as_ref() == Some(&processor_merchant_id)
            })
            .or_else(|| {
                authorizations.iter().position(|authorization| {
                    authorization.authorization_id == authorization_id
                        && authorization.merchant_id == processor_merchant_id
                })
            });

        index
            .and_then(|idx| {
                authorizations.get_mut(idx).map(|auth| {
                    let authorization_updated =
                        AuthorizationUpdateInternal::from(authorization_update)
                            .create_authorization(auth.clone());
                    *auth = authorization_updated.clone();
                    authorization_updated
                })
            })
            .ok_or(
                errors::StorageError::ValueNotFound(format!(
                    "cannot find authorization for authorization_id = {authorization_id} and processor_merchant_id = {processor_merchant_id:?}"
                ))
                .into(),
            )
    }
}
