use std::time::Duration;

use anyhow::Error;
use axum::{
    Json,
    http::StatusCode,
    response::{IntoResponse, Response},
};
use serde::Serialize;

use super::directline_client::DirectLineError;

#[derive(Debug, thiserror::Error)]
pub enum WebChatError {
    #[error("direct line secret not configured for tenant")]
    MissingSecret,
    #[error("invalid request: {0}")]
    BadRequest(&'static str),
    #[error("resource not found: {0}")]
    NotFound(&'static str),
    #[error("direct line error")]
    DirectLine(#[from] DirectLineError),
    #[error("internal server error")]
    Internal(#[source] Error),
}

impl WebChatError {
    pub fn status(&self) -> StatusCode {
        match self {
            WebChatError::MissingSecret => StatusCode::INTERNAL_SERVER_ERROR,
            WebChatError::BadRequest(_) => StatusCode::BAD_REQUEST,
            WebChatError::NotFound(_) => StatusCode::NOT_FOUND,
            WebChatError::DirectLine(DirectLineError::Remote { status, .. }) => {
                if status.is_server_error() {
                    StatusCode::BAD_GATEWAY
                } else {
                    StatusCode::BAD_REQUEST
                }
            }
            WebChatError::DirectLine(_) => StatusCode::BAD_GATEWAY,
            WebChatError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }

    fn retry_after(&self) -> Option<Duration> {
        match self {
            WebChatError::DirectLine(DirectLineError::Remote { retry_after, .. }) => *retry_after,
            _ => None,
        }
    }

    fn message(&self) -> String {
        match self {
            WebChatError::DirectLine(DirectLineError::Remote { .. }) => {
                "Downstream Direct Line error".to_string()
            }
            WebChatError::NotFound(message) => (*message).to_string(),
            WebChatError::Internal(_) => "internal server error".to_string(),
            other => other.to_string(),
        }
    }
}

impl IntoResponse for WebChatError {
    fn into_response(self) -> Response {
        let status = self.status();
        let body = Json(ErrorBody {
            error: self.message(),
        });
        let mut response = (status, body).into_response();
        if let Some(retry_after) = self.retry_after()
            && let Ok(header) =
                axum::http::HeaderValue::from_str(&retry_after.as_secs().to_string())
        {
            response
                .headers_mut()
                .insert(axum::http::header::RETRY_AFTER, header);
        }
        response
    }
}

#[derive(Serialize)]
struct ErrorBody {
    error: String,
}
