//
// Copyright 2023 Wultra s.r.o.
//
// 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.
//

import Foundation

/// Error type reported from the native module.
enum ModuleError: Error {
    /// The initialize or shutdown function is called in the wrong state.
    case wrongState(current: Malwarelytics.State, expected: Malwarelytics.State)
    /// Method is not available due to incomplete configuration.
    case notAvailable(message: String)
    /// Invalid configuration object provided to the function
    case invalidConfig(configPath: String, object: String? = nil)
    /// Missing required configuration property.
    case missingConfig(configPath: String, object: String? = nil)
    /// Invalid parameter provided to the function.
    case invalidParam(paramName: String)
    /// Missing required parameter
    case missingParam(paramName: String)
    /// Generic, unspecified error.
    case genericError(message: String? = nil, exception: NSError? = nil)
}

extension Error {
    /// Convert Error into ErrorInfo
    private func toInfo() -> ErrorInfo {
        if let rnError = self as? ErrorInfo {
            return rnError
        }
        if let moduleError = self as? ModuleError {
            return moduleError.reactError
        }        
        return ErrorInfo(code: "GENERIC_ERROR", message: self.localizedDescription, error: self as NSError)
    }
    
    /// Report error into reject promise block.
    /// - Parameter rejecter: Rejecter that receives the error.
    func report(to rejecter: RCTPromiseRejectBlock) {
        let info = self.toInfo()
        rejecter(info.code, info.message, info.error)
    }
}

fileprivate extension ModuleError {
    var code: String {
        switch self {
        case .wrongState(_, _):     return "WRONG_STATE"
        case .notAvailable(_):      return "METHOD_NOT_AVAILABLE"
        case .invalidConfig(_,_):   return "INVALID_CONFIG"
        case .missingConfig(_,_):   return "MISSING_CONFIG"
        case .invalidParam(_):      return "INVALID_PARAM"
        case .missingParam(_):      return "MISSING_PARAM"
        case .genericError(_,_):    return "GENERIC_ERROR"
        }
    }
    
    var message: String {
        switch self {
        case .wrongState(let current, let expected):
            return "Function can be called only in \(expected.asString) state. The current state is \(current.asString)"
        case .notAvailable(let message):
            return message
        case .missingParam(let paramName):
            return "Required parameter \"\(paramName)\" is missing"
        case .invalidParam(let paramName):
            return "Parameter \"\(paramName)\" is invalid or has wrong type"
        case .invalidConfig(let configPath, let object):
            if let object = object {
                return "Invalid parameter \"\(configPath)\" in configuration object \"\(object)\""
            } else {
                return "Invalid configuration object at path \"\(configPath)\""
            }
        case .missingConfig(let configPath, let object):
            if let object = object {
                return "Missing required parameter \"\(configPath)\" in configuration object \"\(object)\""
            } else {
                return "Missing required parameter in configuration object at path \"\(configPath)\""
            }
        case .genericError(let message, let exception):
            if let exception = exception {
                return message ?? exception.localizedDescription
            } else {
                return message ?? "Uknown error"
            }
        }
    }
    
    var reactError: ErrorInfo {
        return ErrorInfo(code: self.code, message: self.message, error: nil)
    }
}

/// Internal structure containing rich information about the error.
fileprivate struct ErrorInfo: Error {
    /// Error code.
    let code: String
    /// Error message.
    let message: String?
    /// Original exception.
    let error: NSError?
}
