import Foundation

/**
 * Unified Print Error System for iOS
 * Maps to TypeScript PrintErrorCode for consistency
 */
enum PrintErrorCode: String {
    // Connection errors (1xxx)
    case deviceNotFound = "E1001"
    case connectionTimeout = "E1002"
    case connectionRefused = "E1003"
    case connectionFailed = "E1004"

    // Bluetooth errors (2xxx)
    case bluetoothDisabled = "E2001"
    case bluetoothNotPaired = "E2002"
    case bluetoothPermission = "E2003"
    case bluetoothNotSupported = "E2004"

    // Network errors (3xxx)
    case networkUnreachable = "E3001"
    case invalidIpAddress = "E3002"
    case portUnavailable = "E3003"
    case invalidHost = "E3004"

    // Print errors (4xxx)
    case sendFailed = "E4001"
    case printerOffline = "E4002"
    case paperOut = "E4003"
    case coverOpen = "E4004"
    case printHeadError = "E4005"

    // Data errors (5xxx)
    case invalidAddress = "E5001"
    case invalidData = "E5002"
    case encodingError = "E5003"
    case imageDecodeError = "E5004"

    // System errors (9xxx)
    case noPrinters = "E9001"
    case noDocuments = "E9002"
    case nativeModuleError = "E9003"
    case unknownError = "E9999"
}

/**
 * Step in print process where error occurred
 */
enum ErrorStep: String {
    case connect
    case send
    case disconnect
    case test
    case unknown
}

/**
 * Print Error with detailed information
 */
struct PrintError: Error {
    let code: PrintErrorCode
    let message: String
    let step: ErrorStep
    let retryable: Bool
    let suggestion: String?

    init(
        code: PrintErrorCode,
        message: String,
        step: ErrorStep = .unknown,
        retryable: Bool? = nil,
        suggestion: String? = nil
    ) {
        self.code = code
        self.message = message
        self.step = step
        self.retryable = retryable ?? Self.isRetryable(code: code)
        self.suggestion = suggestion ?? Self.getSuggestion(for: code)
    }

    /**
     * Convert to dictionary for React Native
     */
    func toDictionary() -> [String: Any] {
        var dict: [String: Any] = [
            "code": code.rawValue,
            "message": message,
            "step": step.rawValue,
            "retryable": retryable
        ]
        if let suggestion = suggestion {
            dict["suggestion"] = suggestion
        }
        return dict
    }

    /**
     * Determine if error is retryable based on code
     */
    private static func isRetryable(code: PrintErrorCode) -> Bool {
        let retryableCodes: [PrintErrorCode] = [
            .connectionTimeout,
            .connectionRefused,
            .networkUnreachable,
            .sendFailed,
            .printerOffline
        ]
        return retryableCodes.contains(code)
    }

    /**
     * Get suggestion for user based on error code
     */
    private static func getSuggestion(for code: PrintErrorCode) -> String? {
        switch code {
        case .deviceNotFound:
            return "Check if printer is turned on and in range"
        case .connectionTimeout:
            return "Check if printer is ready and try again"
        case .bluetoothDisabled:
            return "Please enable Bluetooth in device settings"
        case .bluetoothNotPaired:
            return "Pair the printer in Bluetooth settings first"
        case .bluetoothPermission:
            return "Grant Bluetooth permission in app settings"
        case .networkUnreachable:
            return "Check network connection and try again"
        case .invalidIpAddress:
            return "Verify the IP address format (e.g., 192.168.1.100)"
        case .paperOut:
            return "Please add paper to the printer"
        case .coverOpen:
            return "Please close the printer cover"
        case .printerOffline:
            return "Turn on the printer and ensure it is ready"
        default:
            return nil
        }
    }
}

/**
 * Factory functions for common errors
 */
extension PrintError {
    // Connection errors
    static func deviceNotFound(_ address: String, step: ErrorStep = .connect) -> PrintError {
        PrintError(
            code: .deviceNotFound,
            message: "Device not found: \(address)",
            step: step
        )
    }

    static func connectionTimeout(_ address: String, timeoutMs: Int, step: ErrorStep = .connect) -> PrintError {
        PrintError(
            code: .connectionTimeout,
            message: "Connection timeout after \(timeoutMs)ms: \(address)",
            step: step
        )
    }

    static func connectionRefused(_ address: String, step: ErrorStep = .connect) -> PrintError {
        PrintError(
            code: .connectionRefused,
            message: "Connection refused: \(address)",
            step: step
        )
    }

    static func connectionFailed(_ address: String, reason: String? = nil, step: ErrorStep = .connect) -> PrintError {
        let message = "Connection failed: \(address)\(reason.map { " - \($0)" } ?? "")"
        return PrintError(
            code: .connectionFailed,
            message: message,
            step: step
        )
    }

    // Bluetooth errors
    static func bluetoothDisabled() -> PrintError {
        PrintError(
            code: .bluetoothDisabled,
            message: "Bluetooth is disabled on device",
            step: .connect
        )
    }

    static func bluetoothNotPaired(_ address: String) -> PrintError {
        PrintError(
            code: .bluetoothNotPaired,
            message: "Device not paired: \(address)",
            step: .connect
        )
    }

    static func bluetoothPermission() -> PrintError {
        PrintError(
            code: .bluetoothPermission,
            message: "Missing Bluetooth permission",
            step: .connect
        )
    }

    static func bluetoothNotSupported() -> PrintError {
        PrintError(
            code: .bluetoothNotSupported,
            message: "Bluetooth not supported on this device",
            step: .connect,
            retryable: false
        )
    }

    // Network errors
    static func networkUnreachable(_ host: String) -> PrintError {
        PrintError(
            code: .networkUnreachable,
            message: "Network unreachable: \(host)",
            step: .connect
        )
    }

    static func invalidIpAddress(_ ip: String) -> PrintError {
        PrintError(
            code: .invalidIpAddress,
            message: "Invalid IP address: \(ip)",
            step: .connect
        )
    }

    // Print errors
    static func sendFailed(_ address: String, reason: String? = nil) -> PrintError {
        let message = "Failed to send data to \(address)\(reason.map { ": \($0)" } ?? "")"
        return PrintError(
            code: .sendFailed,
            message: message,
            step: .send
        )
    }

    static func printerOffline(_ address: String) -> PrintError {
        PrintError(
            code: .printerOffline,
            message: "Printer offline: \(address)",
            step: .send
        )
    }

    // Data errors
    static func invalidAddress(_ address: String) -> PrintError {
        PrintError(
            code: .invalidAddress,
            message: "Invalid address format: \(address). Use bt:MAC, lan:IP:PORT, or ble:UUID",
            step: .connect,
            retryable: false
        )
    }

    static func invalidData(_ reason: String) -> PrintError {
        PrintError(
            code: .invalidData,
            message: "Invalid data: \(reason)",
            step: .send,
            retryable: false
        )
    }

    // System errors
    static func unknown(_ error: Error?) -> PrintError {
        PrintError(
            code: .unknownError,
            message: error?.localizedDescription ?? "Unknown error occurred",
            step: .unknown,
            retryable: false
        )
    }

    /**
     * Parse native error to PrintError
     */
    static func fromError(_ error: Error, address: String? = nil, step: ErrorStep = .unknown) -> PrintError {
        let message = error.localizedDescription

        // Parse based on error type and message
        switch error {
        // Parse PrintError directly
        case let printError as PrintError:
            return printError

        // Generic error parsing
        default:
            // Parse by message content
            if message.contains("not found") || message.contains("Device not found") {
                return deviceNotFound(address ?? "unknown", step: step)
            } else if message.contains("timeout") || message.contains("timed out") {
                return connectionTimeout(address ?? "unknown", timeoutMs: 10000, step: step)
            } else if message.contains("refused") || message.contains("Connection refused") {
                return connectionRefused(address ?? "unknown", step: step)
            } else if message.contains("Network unreachable") || message.contains("unreachable") {
                return networkUnreachable(address ?? "unknown")
            } else if message.contains("Bluetooth") && message.contains("disabled") {
                return bluetoothDisabled()
            } else if message.contains("not paired") || message.contains("Not paired") {
                return bluetoothNotPaired(address ?? "unknown")
            } else if message.contains("permission") || message.contains("Permission") {
                return bluetoothPermission()
            } else if message.contains("Failed to send") {
                return sendFailed(address ?? "unknown", reason: message)
            } else {
                return unknown(error)
            }
        }
    }
}