// Copyright (C) 2024 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Error types and handling for cache download operations.
//!
//! This module defines the primary error type used throughout the cache
//! download system, along with conversion from common error sources.

use std::io;

use netstack_rs::error::HttpErrorCode;

use super::common::CommonError;

/// DNS error codes using HttpErrorCode enum values
const DNS_ERROR_CODES: &[i32] = &[
    HttpErrorCode::HttpCouldntResolveProxy as i32,
    HttpErrorCode::HttpCouldntResolveHost as i32,
];

/// TCP error codes using HttpErrorCode enum values
const TCP_ERROR_CODES: &[i32] = &[
    HttpErrorCode::HttpCouldntConnect as i32,
    HttpErrorCode::HttpSendError as i32,
    HttpErrorCode::HttpRecvError as i32,
];

/// SSL error codes using HttpErrorCode enum values
const SSL_ERROR_CODES: &[i32] = &[
    HttpErrorCode::HttpSslCertproblem as i32,
    HttpErrorCode::HttpSslCipher as i32,
    HttpErrorCode::HttpPeerFailedVerification as i32,
    HttpErrorCode::HttpSslCacertBadfile as i32,
    HttpErrorCode::HttpSslPinnedpubkeynotmatch as i32,
];

/// HTTP error codes using HttpErrorCode enum values
const HTTP_ERROR_CODES: &[i32] = &[
    HttpErrorCode::HttpRemoteAccessDenied as i32,
    HttpErrorCode::HttpHttp2Error as i32,
    HttpErrorCode::HttpPostError as i32,
    HttpErrorCode::HttpTooManyRedirects as i32,
    HttpErrorCode::HttpRemoteDiskFull as i32,
    HttpErrorCode::HttpRemoteFileExists as i32,
    HttpErrorCode::HttpRemoteFileNotFound as i32,
    HttpErrorCode::HttpAuthError as i32,
    HttpErrorCode::HttpNoneErr as i32,
    HttpErrorCode::HttpPartialFile as i32,
    HttpErrorCode::HttpBadContentEncoding as i32,
];

/// OTHERS error codes using HttpErrorCode enum values
const OTHERS_ERROR_CODES: &[i32] = &[
    HttpErrorCode::HttpPermissionDeniedCode as i32,
    HttpErrorCode::HttpParseErrorCode as i32,
    HttpErrorCode::HttpUnsupportedProtocol as i32,
    HttpErrorCode::HttpFailedInit as i32,
    HttpErrorCode::HttpUrlMalformat as i32,
    HttpErrorCode::HttpOutOfMemory as i32,
    HttpErrorCode::HttpUnknownOtherError as i32,
    HttpErrorCode::HttpWeirdServerReply as i32,
    HttpErrorCode::HttpWriteError as i32,
    HttpErrorCode::HttpUploadFailed as i32,
    HttpErrorCode::HttpReadError as i32,
    HttpErrorCode::HttpOperationTimedout as i32,
    HttpErrorCode::HttpTaskCanceled as i32,
    HttpErrorCode::HttpGotNothing as i32,
    HttpErrorCode::HttpFilesizeExceeded as i32,
];

/// Primary error type for cache download operations.
///
/// Encapsulates error information including error code, message, and error
/// kind.
#[derive(Debug)]
pub struct CacheDownloadError {
    /// Numeric error code, if available
    code: Option<i32>,
    /// Human-readable error message
    message: String,
    /// Categorizes the type of error that occurred
    kind: ErrorKind,
}

impl CacheDownloadError {
    /// Returns the error code.
    ///
    /// # Returns
    /// The error code if available, otherwise 0.
    pub fn code(&self) -> i32 {
        self.code.unwrap_or(0)
    }

    /// Returns the error message.
    ///
    /// # Returns
    /// A string slice containing the error message.
    pub fn message(&self) -> &str {
        &self.message
    }

    /// Returns the error kind as an integer code for FFI compatibility.
    ///
    /// # Returns
    /// An integer representation of the error kind.
    pub fn ffi_kind(&self) -> i32 {
        self.kind.clone() as i32
    }

    /// Returns the categorized error kind.
    ///
    /// # Returns
    /// A clone of the error kind.
    pub fn kind(&self) -> ErrorKind {
        self.kind.clone()
    }
}

/// Categorizes the type of error that occurred.
#[derive(Debug, Clone)]
pub enum ErrorKind {
    /// HTTP-related errors, typically from network operations
    Http,
    /// I/O-related errors, typically from file operations
    Io,
    /// DNS-related errors, typically from network operations
    Dns,
    /// TCP-related errors, typically from network operations
    Tcp,
    /// SSL-related errors, typically from network operations
    Ssl,
    /// Others errors, typically from network operations
    Others,
}

impl From<io::Error> for CacheDownloadError {
    /// Converts an I/O error into a cache download error.
    ///
    /// Preserves the OS error code if available and sets the error kind to Io.
    ///
    /// # Arguments
    /// * `err` - The I/O error to convert
    ///
    /// # Returns
    /// A new `CacheDownloadError` with the I/O error information.
    fn from(err: io::Error) -> Self {
        CacheDownloadError {
            code: err.raw_os_error(),
            message: err.to_string(),
            kind: ErrorKind::Io,
        }
    }
}

impl<'a, E> From<&'a E> for CacheDownloadError
where
    E: CommonError,
{
    /// Converts a reference to any type implementing `CommonError` into a cache
    /// download error.
    ///
    /// Sets the error kind based on error code ranges and preserves both the
    /// error code and message.
    ///
    /// # Type Parameters
    /// * `E` - Type implementing `CommonError`
    ///
    /// # Arguments
    /// * `err` - Reference to the error object to convert
    ///
    /// # Returns
    /// A new `CacheDownloadError` with the converted error information.
    fn from(err: &'a E) -> Self {
        let code = err.code();
        let kind = match code {
            code if DNS_ERROR_CODES.contains(&code) => ErrorKind::Dns,
            code if TCP_ERROR_CODES.contains(&code) => ErrorKind::Tcp,
            code if SSL_ERROR_CODES.contains(&code) => ErrorKind::Ssl,
            code if HTTP_ERROR_CODES.contains(&code) => ErrorKind::Http,
            code if OTHERS_ERROR_CODES.contains(&code) => ErrorKind::Others,
            // default case for unknown error codes
            _ => ErrorKind::Others,
        };

        CacheDownloadError {
            code: Some(code),
            message: err.msg().to_string(),
            kind,
        }
    }
}