import Foundation
import CoreBluetooth
import ExternalAccessory
import Network

/**
 * Connection Tester for iOS
 * Tests printer connectivity without keeping connections
 */
class ConnectionTester {

    // Test result types
    enum TestResult {
        case success(deviceName: String?, deviceType: String)
        case failed(error: PrintError)
    }

    private static let defaultTimeoutSec: TimeInterval = 5.0

    /**
     * Test printer connection based on address format
     * @param address Format: "bt:IDENTIFIER", "lan:IP:PORT", or "ble:UUID"
     * @param timeout Timeout in seconds
     * @param completion Callback with test result
     */
    func testPrinter(
        address: String,
        timeout: TimeInterval = defaultTimeoutSec,
        completion: @escaping (TestResult) -> Void
    ) {
        print("[Native:iOS] ConnectionTester.testPrinter - START: \(address)")

        // Parse address format
        let parts = address.split(separator: ":", maxSplits: 1)
        guard parts.count == 2 else {
            completion(.failed(error: .invalidAddress(address)))
            return
        }

        let type = String(parts[0])
        let target = String(parts[1])

        switch type {
        case "bt":
            testBluetooth(identifier: target, timeout: timeout, completion: completion)
        case "lan":
            testLAN(hostPort: target, timeout: timeout, completion: completion)
        case "ble":
            testBLE(uuid: target, timeout: timeout, completion: completion)
        default:
            completion(.failed(error: .invalidAddress(address)))
        }
    }

    /**
     * Test Bluetooth MFi printer connection
     */
    private func testBluetooth(
        identifier: String,
        timeout: TimeInterval,
        completion: @escaping (TestResult) -> Void
    ) {
        print("[Native:iOS] ConnectionTester.testBluetooth - START: \(identifier)")

        DispatchQueue.global(qos: .default).async {
            // Check if accessory exists
            let accessories = EAAccessoryManager.shared().connectedAccessories

            guard let accessory = accessories.first(where: {
                $0.serialNumber == identifier ||
                $0.name == identifier ||
                "\($0.manufacturer)-\($0.modelNumber)" == identifier
            }) else {
                completion(.failed(error: .deviceNotFound("bt:\(identifier)")))
                return
            }

            // Check if accessory supports printing protocol
            let supportedProtocols = ["com.printer.protocol", "jp.star-m.starpro"]
            guard accessory.protocolStrings.contains(where: { supportedProtocols.contains($0) }) else {
                completion(.failed(error: PrintError(
                    code: .bluetoothNotSupported,
                    message: "Device does not support printing protocol",
                    step: .test
                )))
                return
            }

            // Try to create session to verify connectivity
            let protocolString = accessory.protocolStrings.first { supportedProtocols.contains($0) } ?? ""

            if let session = EASession(accessory: accessory, forProtocol: protocolString) {
                // Test successful - close immediately
                session.inputStream?.close()
                session.outputStream?.close()

                print("[Native:iOS] ConnectionTester.testBluetooth - SUCCESS: \(accessory.name)")
                completion(.success(
                    deviceName: accessory.name,
                    deviceType: "Bluetooth MFi"
                ))
            } else {
                completion(.failed(error: .connectionFailed("bt:\(identifier)", reason: "Cannot create session")))
            }
        }
    }

    /**
     * Test LAN/TCP printer connection
     */
    private func testLAN(
        hostPort: String,
        timeout: TimeInterval,
        completion: @escaping (TestResult) -> Void
    ) {
        // Parse host and port
        let parts = hostPort.split(separator: ":")
        let host = String(parts[0])
        let port = parts.count > 1 ? Int(parts[1]) ?? 9100 : 9100

        print("[Native:iOS] ConnectionTester.testLAN - START: \(host):\(port)")

        // Validate IP format
        guard isValidIPAddress(host) else {
            completion(.failed(error: .invalidIpAddress(host)))
            return
        }

        // Test connection using Network framework
        let connection = NWConnection(
            host: NWEndpoint.Host(host),
            port: NWEndpoint.Port(integerLiteral: NWEndpoint.Port.IntegerLiteralType(port)),
            using: .tcp
        )

        var timeoutTimer: DispatchWorkItem?
        var hasCompleted = false

        // Set timeout
        timeoutTimer = DispatchWorkItem {
            if !hasCompleted {
                hasCompleted = true
                connection.cancel()
                completion(.failed(error: .connectionTimeout("lan:\(hostPort)", timeoutMs: Int(timeout * 1000))))
            }
        }
        DispatchQueue.global().asyncAfter(deadline: .now() + timeout, execute: timeoutTimer!)

        // Monitor connection state
        connection.stateUpdateHandler = { state in
            switch state {
            case .ready:
                // Connection successful
                if !hasCompleted {
                    hasCompleted = true
                    timeoutTimer?.cancel()
                    connection.cancel()

                    print("[Native:iOS] ConnectionTester.testLAN - SUCCESS: \(host):\(port)")
                    completion(.success(
                        deviceName: "\(host):\(port)",
                        deviceType: "Network Printer"
                    ))
                }

            case .failed(let error):
                if !hasCompleted {
                    hasCompleted = true
                    timeoutTimer?.cancel()

                    let errorMessage = error.localizedDescription
                    if errorMessage.contains("refused") {
                        completion(.failed(error: .connectionRefused("lan:\(hostPort)")))
                    } else if errorMessage.contains("unreachable") {
                        completion(.failed(error: .networkUnreachable(host)))
                    } else {
                        completion(.failed(error: .connectionFailed("lan:\(hostPort)", reason: errorMessage)))
                    }
                }

            case .waiting(let error):
                print("[Native:iOS] ConnectionTester.testLAN - PROCESS: Waiting with error: \(error)")
                // Don't complete yet, might recover

            default:
                break
            }
        }

        // Start connection
        connection.start(queue: .global())
    }

    /**
     * Test BLE printer connection
     */
    private func testBLE(
        uuid: String,
        timeout: TimeInterval,
        completion: @escaping (TestResult) -> Void
    ) {
        print("[Native:iOS] ConnectionTester.testBLE - START: \(uuid)")

        // Validate UUID format first
        guard let deviceUUID = UUID(uuidString: uuid) else {
            print("[Native:iOS] ConnectionTester.testBLE - ERROR: Invalid UUID format: \(uuid)")
            completion(.failed(error: .invalidAddress("ble:\(uuid)")))
            return
        }

        // For BLE on iOS:
        // 1. We can't test without actually connecting
        // 2. Discovery already confirmed device exists and is advertising
        // 3. Real connection test happens during print

        // Check if this was a recently discovered device (simple validation)
        // In production, you could store discovered UUIDs in memory to verify

        print("[Native:iOS] ConnectionTester.testBLE - PROCESS: UUID valid: \(deviceUUID)")

        // Return success with minimal delay
        // The actual connection will be tested when printing
        completion(.success(
            deviceName: "BLE Printer (\(uuid.prefix(8))...)",
            deviceType: "BLE"
        ))
    }

    /**
     * Convert TestResult to dictionary for React Native
     */
    func testResultToDictionary(_ result: TestResult) -> [String: Any] {
        switch result {
        case .success(let deviceName, let deviceType):
            var dict: [String: Any] = [
                "success": true,
                "deviceType": deviceType
            ]
            if let name = deviceName {
                dict["deviceName"] = name
            }
            return dict

        case .failed(let error):
            return [
                "success": false,
                "error": error.toDictionary()
            ]
        }
    }

    // Helper function to validate IP address
    private func isValidIPAddress(_ ip: String) -> Bool {
        let parts = ip.split(separator: ".")
        guard parts.count == 4 else { return false }

        for part in parts {
            guard let num = Int(part), num >= 0 && num <= 255 else {
                return false
            }
        }
        return true
    }
}