use actix_web::{http::StatusCode, HttpResponse, ResponseError};
use tracing_error::SpanTrace;

pub(crate) struct Error {
    context: SpanTrace,
    kind: UploadError,
}

impl std::fmt::Debug for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "{}", self.kind)
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "{}", self.kind)?;
        std::fmt::Display::fmt(&self.context, f)
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.kind.source()
    }
}

impl<T> From<T> for Error
where
    UploadError: From<T>,
{
    fn from(error: T) -> Self {
        Error {
            kind: UploadError::from(error),
            context: SpanTrace::capture(),
        }
    }
}

impl From<sled::transaction::TransactionError<Error>> for Error {
    fn from(e: sled::transaction::TransactionError<Error>) -> Self {
        match e {
            sled::transaction::TransactionError::Abort(t) => t,
            sled::transaction::TransactionError::Storage(e) => e.into(),
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub(crate) enum UploadError {
    #[error("Couln't upload file, {0}")]
    Upload(#[from] actix_form_data::Error),

    #[error("Error in DB, {0}")]
    Db(#[from] sled::Error),

    #[error("Error parsing string, {0}")]
    ParseString(#[from] std::string::FromUtf8Error),

    #[error("Error interacting with filesystem, {0}")]
    Io(#[from] std::io::Error),

    #[error(transparent)]
    PathGenerator(#[from] storage_path_generator::PathError),

    #[error(transparent)]
    StripPrefix(#[from] std::path::StripPrefixError),

    #[error(transparent)]
    FileStore(#[from] crate::store::file_store::FileError),

    #[cfg(feature = "object-storage")]
    #[error(transparent)]
    ObjectStore(#[from] crate::store::object_store::ObjectError),

    #[error("Provided process path is invalid")]
    ParsePath,

    #[error("Failed to acquire the semaphore")]
    Semaphore,

    #[error("Panic in blocking operation")]
    Canceled,

    #[error("No files present in upload")]
    NoFiles,

    #[error("Requested a file that doesn't exist")]
    MissingAlias,

    #[error("Alias directed to missing file")]
    MissingFile,

    #[error("Provided token did not match expected token")]
    InvalidToken,

    #[error("Unsupported image format")]
    UnsupportedFormat,

    #[error("Invalid media dimensions")]
    Dimensions,

    #[error("Unable to download image, bad response {0}")]
    Download(actix_web::http::StatusCode),

    #[error("Unable to download image, {0}")]
    Payload(#[from] awc::error::PayloadError),

    #[error("Unable to send request, {0}")]
    SendRequest(String),

    #[error("No filename provided in request")]
    MissingFilename,

    #[error("Error converting Path to String")]
    Path,

    #[error("Tried to save an image with an already-taken name")]
    DuplicateAlias,

    #[error("{0}")]
    Json(#[from] serde_json::Error),

    #[error("Range header not satisfiable")]
    Range,

    #[error(transparent)]
    Limit(#[from] super::LimitError),
}

impl From<awc::error::SendRequestError> for UploadError {
    fn from(e: awc::error::SendRequestError) -> Self {
        UploadError::SendRequest(e.to_string())
    }
}

impl From<actix_web::error::BlockingError> for UploadError {
    fn from(_: actix_web::error::BlockingError) -> Self {
        UploadError::Canceled
    }
}

impl From<tokio::sync::AcquireError> for UploadError {
    fn from(_: tokio::sync::AcquireError) -> Self {
        UploadError::Semaphore
    }
}

impl ResponseError for Error {
    fn status_code(&self) -> StatusCode {
        match self.kind {
            UploadError::DuplicateAlias
            | UploadError::Limit(_)
            | UploadError::NoFiles
            | UploadError::Upload(_) => StatusCode::BAD_REQUEST,
            UploadError::MissingAlias | UploadError::MissingFilename => StatusCode::NOT_FOUND,
            UploadError::InvalidToken => StatusCode::FORBIDDEN,
            UploadError::Range => StatusCode::RANGE_NOT_SATISFIABLE,
            _ => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }

    fn error_response(&self) -> HttpResponse {
        HttpResponse::build(self.status_code())
            .content_type("application/json")
            .body(
                serde_json::to_string(&serde_json::json!({ "msg": self.kind.to_string() }))
                    .unwrap_or_else(|_| r#"{"msg":"Request failed"}"#.to_string()),
            )
    }
}
