//! Webhooks resource for managing webhook endpoints.

use crate::client::Sendly;
use crate::error::Result;
use crate::models::{
    CreateWebhookRequest, ListDeliveriesOptions, UpdateWebhookRequest, Webhook,
    WebhookCreatedResponse, WebhookDelivery, WebhookDeliveryList, WebhookSecretRotation,
    WebhookTestResult,
};
use serde::{Deserialize, Serialize};

/// Options for [`WebhooksResource::redeliver`]. Use
/// `RedeliverOptions::default()` to accept server defaults (last 24h,
/// statuses `["failed", "cancelled"]`, limit 1000).
#[derive(Debug, Default, Clone, Serialize)]
pub struct RedeliverOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub since: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub until: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "event_types")]
    pub event_types: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub statuses: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

/// Options for [`WebhooksResource::backfill`]. Use
/// `BackfillOptions::default()` to accept server defaults.
#[derive(Debug, Default, Clone, Serialize)]
pub struct BackfillOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub since: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub until: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "event_types")]
    pub event_types: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

/// Webhooks resource for managing webhook endpoints.
pub struct WebhooksResource<'a> {
    client: &'a Sendly,
}

#[derive(Debug, Deserialize)]
struct WebhookResponse {
    #[serde(default)]
    webhook: Option<Webhook>,
    #[serde(default)]
    data: Option<Webhook>,
    #[serde(flatten)]
    flat: Option<Webhook>,
}

#[derive(Debug, Deserialize)]
struct WebhookListResponse {
    #[serde(default)]
    webhooks: Option<Vec<Webhook>>,
    #[serde(default)]
    data: Option<Vec<Webhook>>,
}

#[derive(Debug, Deserialize)]
struct DeliveryResponse {
    #[serde(default)]
    delivery: Option<WebhookDelivery>,
    #[serde(default)]
    data: Option<WebhookDelivery>,
}

impl<'a> WebhooksResource<'a> {
    pub(crate) fn new(client: &'a Sendly) -> Self {
        Self { client }
    }

    /// Creates a new webhook.
    ///
    /// # Arguments
    ///
    /// * `url` - URL to receive webhook events
    /// * `events` - List of event types to subscribe to
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use sendly::Sendly;
    ///
    /// # async fn example() -> Result<(), sendly::Error> {
    /// let client = Sendly::new("sk_live_v1_xxx");
    ///
    /// let response = client.webhooks().create(
    ///     "https://example.com/webhook",
    ///     vec!["message.delivered", "message.failed"],
    /// ).await?;
    ///
    /// println!("Webhook created: {:?}", response.get_webhook());
    /// println!("Secret: {}", response.secret);
    /// # Ok(())
    /// # }
    /// ```
    pub async fn create(
        &self,
        url: impl Into<String>,
        events: Vec<impl Into<String>>,
    ) -> Result<WebhookCreatedResponse> {
        let request = CreateWebhookRequest {
            url: url.into(),
            events: events.into_iter().map(|e| e.into()).collect(),
            mode: None,
            api_version: None,
        };

        self.create_with_options(request).await
    }

    /// Creates a new webhook with full options.
    pub async fn create_with_options(
        &self,
        request: CreateWebhookRequest,
    ) -> Result<WebhookCreatedResponse> {
        let response = self.client.post("/webhooks", &request).await?;
        let result: WebhookCreatedResponse = response.json().await?;
        Ok(result)
    }

    /// Lists all webhooks.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use sendly::Sendly;
    ///
    /// # async fn example() -> Result<(), sendly::Error> {
    /// let client = Sendly::new("sk_live_v1_xxx");
    ///
    /// let webhooks = client.webhooks().list().await?;
    /// for webhook in webhooks {
    ///     println!("Webhook: {} -> {}", webhook.id, webhook.url);
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub async fn list(&self) -> Result<Vec<Webhook>> {
        let response = self.client.get("/webhooks", &[]).await?;
        let result: WebhookListResponse = response.json().await?;

        Ok(result.webhooks.or(result.data).unwrap_or_default())
    }

    /// Gets a webhook by ID.
    ///
    /// # Arguments
    ///
    /// * `id` - Webhook ID
    pub async fn get(&self, id: impl AsRef<str>) -> Result<Webhook> {
        let path = format!("/webhooks/{}", id.as_ref());
        let response = self.client.get(&path, &[]).await?;
        let result: WebhookResponse = response.json().await?;

        Ok(result
            .webhook
            .or(result.data)
            .or(result.flat)
            .unwrap_or_else(|| Webhook {
                id: String::new(),
                url: String::new(),
                events: Vec::new(),
                mode: crate::models::WebhookMode::All,
                is_active: true,
                failure_count: 0,
                circuit_state: crate::models::CircuitState::Closed,
                api_version: None,
                total_deliveries: 0,
                successful_deliveries: 0,
                success_rate: 0.0,
                last_delivery_at: None,
                created_at: None,
                updated_at: None,
            }))
    }

    /// Updates a webhook.
    ///
    /// # Arguments
    ///
    /// * `id` - Webhook ID
    /// * `request` - Update options
    pub async fn update(
        &self,
        id: impl AsRef<str>,
        request: UpdateWebhookRequest,
    ) -> Result<Webhook> {
        let path = format!("/webhooks/{}", id.as_ref());
        let response = self.client.patch(&path, &request).await?;
        let result: WebhookResponse = response.json().await?;

        Ok(result
            .webhook
            .or(result.data)
            .or(result.flat)
            .unwrap_or_else(|| Webhook {
                id: String::new(),
                url: String::new(),
                events: Vec::new(),
                mode: crate::models::WebhookMode::All,
                is_active: true,
                failure_count: 0,
                circuit_state: crate::models::CircuitState::Closed,
                api_version: None,
                total_deliveries: 0,
                successful_deliveries: 0,
                success_rate: 0.0,
                last_delivery_at: None,
                created_at: None,
                updated_at: None,
            }))
    }

    /// Deletes a webhook.
    ///
    /// # Arguments
    ///
    /// * `id` - Webhook ID
    pub async fn delete(&self, id: impl AsRef<str>) -> Result<()> {
        let path = format!("/webhooks/{}", id.as_ref());
        self.client.delete(&path).await?;
        Ok(())
    }

    /// Tests a webhook endpoint.
    ///
    /// # Arguments
    ///
    /// * `id` - Webhook ID
    pub async fn test(&self, id: impl AsRef<str>) -> Result<WebhookTestResult> {
        let path = format!("/webhooks/{}/test", id.as_ref());
        let response = self.client.post(&path, &()).await?;
        let result: WebhookTestResult = response.json().await?;
        Ok(result)
    }

    /// Resets the circuit breaker for a webhook.
    ///
    /// # Arguments
    ///
    /// * `id` - Webhook ID
    pub async fn reset_circuit(&self, id: impl AsRef<str>) -> Result<serde_json::Value> {
        let path = format!("/webhooks/{}/reset-circuit", id.as_ref());
        let response = self.client.post(&path, &()).await?;
        let result: serde_json::Value = response.json().await?;
        Ok(result)
    }

    /// Replay failed or cancelled webhook deliveries from the audit log.
    ///
    /// Use after a customer endpoint has recovered from an outage to re-fire
    /// deliveries we recorded but couldn't deliver. Each replay creates a
    /// new delivery row preserving the original `event_id` so customers can
    /// dedupe. Rejects with HTTP 409 if the circuit is currently open —
    /// call [`reset_circuit`](Self::reset_circuit) first.
    ///
    /// # Arguments
    ///
    /// * `id` - Webhook ID
    /// * `options` - Window and filter options; `RedeliverOptions::default()`
    ///   uses server defaults (last 24h, statuses `["failed", "cancelled"]`,
    ///   limit 1000).
    pub async fn redeliver(
        &self,
        id: impl AsRef<str>,
        options: RedeliverOptions,
    ) -> Result<serde_json::Value> {
        let path = format!("/webhooks/{}/redeliver", id.as_ref());
        let response = self.client.post(&path, &options).await?;
        let result: serde_json::Value = response.json().await?;
        Ok(result)
    }

    /// Backfill missed webhook events from the underlying message log.
    ///
    /// Use when a circuit-breaker outage left events with no audit row (the
    /// case [`redeliver`](Self::redeliver) cannot recover). Synthesized
    /// events have fresh IDs; clients should dedupe by
    /// `event.data.object.id` (the message ID). Rejects with HTTP 409 if
    /// the circuit is currently open — call
    /// [`reset_circuit`](Self::reset_circuit) first.
    ///
    /// # Arguments
    ///
    /// * `id` - Webhook ID
    /// * `options` - Window and filter options; `BackfillOptions::default()`
    ///   uses server defaults (last 24h, all subscribed message events,
    ///   limit 1000).
    pub async fn backfill(
        &self,
        id: impl AsRef<str>,
        options: BackfillOptions,
    ) -> Result<serde_json::Value> {
        let path = format!("/webhooks/{}/backfill", id.as_ref());
        let response = self.client.post(&path, &options).await?;
        let result: serde_json::Value = response.json().await?;
        Ok(result)
    }

    /// Rotates a webhook's secret.
    ///
    /// # Arguments
    ///
    /// * `id` - Webhook ID
    pub async fn rotate_secret(&self, id: impl AsRef<str>) -> Result<WebhookSecretRotation> {
        let path = format!("/webhooks/{}/rotate-secret", id.as_ref());
        let response = self.client.post(&path, &()).await?;
        let result: WebhookSecretRotation = response.json().await?;
        Ok(result)
    }

    /// Lists delivery attempts for a webhook.
    ///
    /// # Arguments
    ///
    /// * `id` - Webhook ID
    /// * `options` - Query options
    pub async fn list_deliveries(
        &self,
        id: impl AsRef<str>,
        options: Option<ListDeliveriesOptions>,
    ) -> Result<WebhookDeliveryList> {
        let path = format!("/webhooks/{}/deliveries", id.as_ref());
        let query = options.unwrap_or_default().to_query_params();
        let response = self.client.get(&path, &query).await?;
        let result: WebhookDeliveryList = response.json().await?;
        Ok(result)
    }

    /// Gets a specific delivery attempt.
    ///
    /// # Arguments
    ///
    /// * `webhook_id` - Webhook ID
    /// * `delivery_id` - Delivery ID
    pub async fn get_delivery(
        &self,
        webhook_id: impl AsRef<str>,
        delivery_id: impl AsRef<str>,
    ) -> Result<WebhookDelivery> {
        let path = format!(
            "/webhooks/{}/deliveries/{}",
            webhook_id.as_ref(),
            delivery_id.as_ref()
        );
        let response = self.client.get(&path, &[]).await?;
        let result: DeliveryResponse = response.json().await?;

        Ok(result
            .delivery
            .or(result.data)
            .unwrap_or_else(|| WebhookDelivery {
                id: String::new(),
                webhook_id: String::new(),
                event_type: String::new(),
                http_status: 0,
                success: false,
                attempt_number: 1,
                error_message: None,
                response_time_ms: 0,
                created_at: None,
            }))
    }

    /// Retries a failed delivery.
    ///
    /// # Arguments
    ///
    /// * `webhook_id` - Webhook ID
    /// * `delivery_id` - Delivery ID
    pub async fn retry_delivery(
        &self,
        webhook_id: impl AsRef<str>,
        delivery_id: impl AsRef<str>,
    ) -> Result<WebhookDelivery> {
        let path = format!(
            "/webhooks/{}/deliveries/{}/retry",
            webhook_id.as_ref(),
            delivery_id.as_ref()
        );
        let response = self.client.post(&path, &()).await?;
        let result: DeliveryResponse = response.json().await?;

        Ok(result
            .delivery
            .or(result.data)
            .unwrap_or_else(|| WebhookDelivery {
                id: String::new(),
                webhook_id: String::new(),
                event_type: String::new(),
                http_status: 0,
                success: false,
                attempt_number: 1,
                error_message: None,
                response_time_ms: 0,
                created_at: None,
            }))
    }

    /// Lists available webhook event types.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use sendly::Sendly;
    ///
    /// # async fn example() -> Result<(), sendly::Error> {
    /// let client = Sendly::new("sk_live_v1_xxx");
    ///
    /// let event_types = client.webhooks().list_event_types().await?;
    /// for event_type in event_types {
    ///     println!("Event type: {}", event_type);
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub async fn list_event_types(&self) -> Result<Vec<String>> {
        #[derive(Debug, Deserialize)]
        struct EventType {
            #[serde(rename = "type")]
            event_type: String,
        }

        #[derive(Debug, Deserialize)]
        struct EventTypesResponse {
            #[serde(default)]
            events: Vec<EventType>,
        }

        let response = self.client.get("/webhooks/event-types", &[]).await?;
        let result: EventTypesResponse = response.json().await?;

        Ok(result.events.into_iter().map(|e| e.event_type).collect())
    }
}
