use domain_types::{
    connector_types::EventContext, errors::WebhookError,
    payment_method_data::PaymentMethodDataTypes, router_data::ConnectorSpecificConfig,
    utils::ForeignTryFrom,
};
use error_stack::ResultExt;
use grpc_api_types::payments::{
    DisputeResponse, EventContent, EventServiceHandleResponse, EventServiceParseResponse,
    PaymentServiceGetResponse, RefundResponse, WebhookEventType,
};

use crate::types::ConnectorData;

/// Core webhook event processing shared by both the gRPC server and the FFI layer.
///
/// Caller is responsible for:
/// 1. Parsing `request_details` and `webhook_secrets` from the raw proto payload.
/// 2. Computing `source_verified` (the gRPC server may use an async external call;
///    the FFI layer uses only synchronous local verification).
///
/// This function then: determines the event type, dispatches to the appropriate
/// content helper, and assembles the final `EventServiceHandleResponse`.
pub fn process_webhook_event<
    T: PaymentMethodDataTypes
        + Default
        + Eq
        + std::fmt::Debug
        + Send
        + serde::Serialize
        + serde::de::DeserializeOwned
        + Clone
        + Sync
        + 'static,
>(
    connector_data: ConnectorData<T>,
    request_details: domain_types::connector_types::RequestDetails,
    webhook_secrets: Option<domain_types::connector_types::ConnectorWebhookSecrets>,
    connector_config: Option<ConnectorSpecificConfig>,
    source_verified: bool,
    merchant_event_id: Option<String>,
    event_context: Option<EventContext>,
) -> error_stack::Result<EventServiceHandleResponse, WebhookError> {
    let event_type = connector_data
        .connector
        .get_event_type(request_details.clone())?;

    let api_event_type = WebhookEventType::foreign_try_from(event_type.clone())
        .change_context(WebhookError::WebhookProcessingFailed)?;

    let event_content = if event_type.is_payment_event() {
        get_payments_webhook_content(
            connector_data.clone(),
            request_details.clone(),
            webhook_secrets,
            connector_config.clone(),
            event_context.clone(),
        )?
    } else if event_type.is_refund_event() {
        get_refunds_webhook_content(
            connector_data.clone(),
            request_details.clone(),
            webhook_secrets,
            connector_config.clone(),
        )?
    } else if event_type.is_dispute_event() {
        get_disputes_webhook_content(
            connector_data.clone(),
            request_details.clone(),
            webhook_secrets,
            connector_config.clone(),
        )?
    } else {
        // Default: treat as payment event (mandate, payout, recovery, misc).
        get_payments_webhook_content(
            connector_data.clone(),
            request_details.clone(),
            webhook_secrets,
            connector_config.clone(),
            event_context,
        )?
    };

    let ack = connector_data.connector.get_webhook_api_response(
        request_details.clone(),
        None,
        connector_config,
    )?;
    let event_ack_response = Some(grpc_api_types::payments::EventAckResponse {
        status_code: ack.status_code,
        headers: ack.headers.into_iter().collect(),
        body: ack.body.unwrap_or_default(),
    });

    Ok(EventServiceHandleResponse {
        event_type: api_event_type.into(),
        event_content: Some(event_content),
        source_verified,
        merchant_event_id,
        event_ack_response,
        supported_integrity_checks: vec![],
    })
}

pub fn parse_webhook_event<
    T: PaymentMethodDataTypes + std::fmt::Debug + Default + Send + Sync + 'static,
>(
    connector_data: ConnectorData<T>,
    request_details: domain_types::connector_types::RequestDetails,
) -> error_stack::Result<EventServiceParseResponse, WebhookError> {
    let event_type = connector_data
        .connector
        .get_event_type(request_details.clone())?;

    let api_event_type = WebhookEventType::foreign_try_from(event_type)
        .change_context(WebhookError::WebhookProcessingFailed)?;

    let domain_reference = connector_data
        .connector
        .get_webhook_event_reference(request_details)
        .attach_printable("Failed to extract event reference from webhook payload")?;

    Ok(EventServiceParseResponse {
        reference: domain_reference.map(Into::into),
        event_type: Some(api_event_type.into()),
    })
}

pub fn get_payments_webhook_content<
    T: PaymentMethodDataTypes + std::fmt::Debug + Default + Send + Sync + 'static,
>(
    connector_data: ConnectorData<T>,
    request_details: domain_types::connector_types::RequestDetails,
    webhook_secrets: Option<domain_types::connector_types::ConnectorWebhookSecrets>,
    connector_config: Option<ConnectorSpecificConfig>,
    event_context: Option<EventContext>,
) -> error_stack::Result<EventContent, WebhookError> {
    let webhook_details = connector_data
        .connector
        .process_payment_webhook(
            request_details.clone(),
            webhook_secrets,
            connector_config,
            event_context,
        )
        .attach_printable("Failed to process payment webhook from connector")?;

    let response = PaymentServiceGetResponse::foreign_try_from(webhook_details)
        .change_context(WebhookError::WebhookProcessingFailed)?;

    Ok(EventContent {
        content: Some(grpc_api_types::payments::event_content::Content::PaymentsResponse(response)),
    })
}

pub fn get_refunds_webhook_content<
    T: PaymentMethodDataTypes
        + Default
        + Eq
        + std::fmt::Debug
        + Send
        + serde::Serialize
        + serde::de::DeserializeOwned
        + Clone
        + Sync
        + 'static,
>(
    connector_data: ConnectorData<T>,
    request_details: domain_types::connector_types::RequestDetails,
    webhook_secrets: Option<domain_types::connector_types::ConnectorWebhookSecrets>,
    connector_config: Option<ConnectorSpecificConfig>,
) -> error_stack::Result<EventContent, WebhookError> {
    let webhook_details = connector_data
        .connector
        .process_refund_webhook(request_details, webhook_secrets, connector_config)
        .attach_printable("Failed to process refund webhook from connector")?;

    let response = RefundResponse::foreign_try_from(webhook_details)
        .change_context(WebhookError::WebhookProcessingFailed)?;

    Ok(EventContent {
        content: Some(grpc_api_types::payments::event_content::Content::RefundsResponse(response)),
    })
}

pub fn get_disputes_webhook_content<
    T: PaymentMethodDataTypes
        + Default
        + Eq
        + std::fmt::Debug
        + Send
        + serde::Serialize
        + serde::de::DeserializeOwned
        + Clone
        + Sync
        + 'static,
>(
    connector_data: ConnectorData<T>,
    request_details: domain_types::connector_types::RequestDetails,
    webhook_secrets: Option<domain_types::connector_types::ConnectorWebhookSecrets>,
    connector_config: Option<ConnectorSpecificConfig>,
) -> error_stack::Result<EventContent, WebhookError> {
    let webhook_details = connector_data
        .connector
        .process_dispute_webhook(request_details, webhook_secrets, connector_config)
        .attach_printable("Failed to process dispute webhook from connector")?;

    let response = DisputeResponse::foreign_try_from(webhook_details)
        .change_context(WebhookError::WebhookProcessingFailed)?;

    Ok(EventContent {
        content: Some(grpc_api_types::payments::event_content::Content::DisputesResponse(response)),
    })
}
