import Foundation
import UIKit

/**
 * Printing Module - Manages printing with auto-reconnect and connection pooling
 * Handles connection lifecycle automatically for better reliability
 */
@objc class PrintingModule: NSObject {

    private static let defaultTimeoutSec: TimeInterval = 10.0
    private static let maxRetries = 2
    private static let retryDelayMs = 500
    private static let connectionIdleTimeSec: TimeInterval = 30.0

    // Connection pool for keepAlive support
    private var activeConnections: [String: ConnectionEntry] = [:]
    private let connectionQueue = DispatchQueue(label: "printing.connections", attributes: .concurrent)

    // Background timer for cleaning idle connections
    private var cleanupTimer: Timer?

    /**
     * Connection pool entry with last used timestamp
     */
    private struct ConnectionEntry {
        let connection: PrintConnection
        var lastUsed: Date = Date()
    }

    /**
     * Print result for React Native
     */
    struct PrintResult {
        let success: Bool
        let error: PrintError?

        func toDictionary() -> [String: Any] {
            var dict: [String: Any] = ["success": success]
            if let error = error {
                dict["error"] = error.toDictionary()
            }
            return dict
        }
    }

    override init() {
        super.init()
        // Start cleanup timer
        startCleanupTimer()
    }

    deinit {
        cleanup()
    }

    /**
     * Main print function with auto-reconnect and error handling
     */
    func printRaw(
        address: String,
        data: Data,
        keepAlive: Bool = false,
        timeoutSec: TimeInterval = defaultTimeoutSec
    ) async -> PrintResult {
        print("[Native:iOS] PrintingModule.printRaw - START")
        print("[Native:iOS] PrintingModule.printRaw - PARAMS: address=\(address), size=\(data.count), keepAlive=\(keepAlive)")

        do {
            // Step 1: Get or create connection
            let connectionEntry = try await getOrCreateConnection(address: address, timeoutSec: timeoutSec)

            guard let connectionEntry = connectionEntry else {
                return PrintResult(
                    success: false,
                    error: .connectionFailed(
                        address,
                        reason: "Failed to establish connection after \(Self.maxRetries) retries",
                        step: .connect
                    )
                )
            }

            // Step 2: Send data
            do {
                try await connectionEntry.connection.send(data: data)

                // Update last used time
                connectionQueue.async(flags: .barrier) {
                    self.activeConnections[address]?.lastUsed = Date()
                }

                print("[Native:iOS] PrintingModule.printRaw - SUCCESS: Data sent to \(address)")

                // Wait for data transmission + processing to complete
                let waitMs = calculateMinimumWaitTimeAfterSend(dataSize: data.count, address: address)
                if waitMs > 0 {
                    print("[Native:iOS] PrintingModule.printRaw - WAIT: \(waitMs)ms after send for \(data.count) bytes")
                    try? await Task.sleep(nanoseconds: UInt64(waitMs) * 1_000_000)
                }

                // Step 3: Handle keepAlive
                if keepAlive {
                    // Keep in pool
                    connectionQueue.async(flags: .barrier) {
                        self.activeConnections[address] = connectionEntry
                    }
                } else {

                    // Add final safety buffer before closing connection
                    let safetyMs = getFinalSafetyBufferBeforeClose(address: address)
                    if safetyMs > 0 {
                        print("[Native:iOS] PrintingModule.printRaw - SAFETY: \(safetyMs)ms buffer before close")
                        try? await Task.sleep(nanoseconds: UInt64(safetyMs) * 1_000_000)
                    }

                    // Remove from pool and close
                    connectionQueue.async(flags: .barrier) {
                        self.activeConnections.removeValue(forKey: address)
                    }
                    connectionEntry.connection.close()
                }

                return PrintResult(success: true, error: nil)

            } catch let error as PrintError {
                // Send failed - remove bad connection
                connectionQueue.async(flags: .barrier) {
                    self.activeConnections.removeValue(forKey: address)
                }
                connectionEntry.connection.close()

                print("[Native:iOS] PrintingModule.printRaw - ERROR: Send failed - \(error)")
                return PrintResult(success: false, error: error)
            }

        } catch let error as PrintError {
            print("[Native:iOS] PrintingModule.printRaw - ERROR: \(error)")
            return PrintResult(success: false, error: error)
        } catch {
            print("[Native:iOS] PrintingModule.printRaw - ERROR: Unexpected - \(error)")
            return PrintResult(
                success: false,
                error: .fromError(error, address: address)
            )
        }
    }

    /**
     * Print image from file path with automatic processing
     * Now uses file path instead of base64 for better performance
     */
    func printImage(
        address: String,
        imagePath: String,
        widthPx: Int = 384,
        paperWidthMm: Int? = nil,
        keepAlive: Bool = false,
        timeoutSec: TimeInterval = defaultTimeoutSec,
        isCutPaper: Bool = false,
        marginMm: Double = 0.0,
        align: Int = 0
    ) async -> PrintResult {
        print("[Native:iOS] PrintingModule.printImage - START")
        print("[Native:iOS] PrintingModule.printImage - PARAMS: address=\(address), path=\(imagePath), width=\(widthPx), paper=\(paperWidthMm ?? 0), margin=\(marginMm), align=\(align)")

        // Clean path (remove file:// prefix if present)
        let cleanPath: String
        if imagePath.hasPrefix("file://") {
            cleanPath = String(imagePath.dropFirst(7))
        } else if imagePath.hasPrefix("file:") {
            cleanPath = String(imagePath.dropFirst(5))
        } else {
            cleanPath = imagePath
        }

        print("[Native:iOS] PrintingModule.printImage - CLEAN PATH: \(cleanPath)")

        // Load image from file
        guard let image = UIImage(contentsOfFile: cleanPath) else {
            print("[Native:iOS] PrintingModule.printImage - ERROR: Failed to load image from \(cleanPath)")
            return PrintResult(
                success: false,
                error: PrintError(
                    code: .imageDecodeError,
                    message: "Failed to load image from path: \(cleanPath)"
                )
            )
        }

        print("[Native:iOS] PrintingModule.printImage - LOADED: \(image.size.width)x\(image.size.height)")

        // Convert align from Int to String
        let alignStr: String
        switch align {
        case 1:
            alignStr = "center"
        case 2:
            alignStr = "right"
        default:
            alignStr = "left"
        }

        // Process image using Zywell logic for proven quality
        let rasterData = ZywellImageProcessor.processImageFile(
            imagePath: cleanPath,
            widthPx: widthPx,
            paperWidthMm: paperWidthMm ?? 80,
            isCutPaper: isCutPaper,
            align: alignStr,
            marginMm: Int(marginMm)
        )

        if rasterData.isEmpty {
            return PrintResult(
                success: false,
                error: PrintError(
                    code: .imageDecodeError,
                    message: "Failed to process image"
                )
            )
        }

        // Send to printer
        return await printRaw(
            address: address,
            data: rasterData,
            keepAlive: keepAlive,
            timeoutSec: timeoutSec
        )
    }

    /**
     * Get existing connection or create new one with retry
     */
    private func getOrCreateConnection(
        address: String,
        timeoutSec: TimeInterval
    ) async throws -> ConnectionEntry? {
        // Check if we have an active connection
        var existingEntry: ConnectionEntry?
        connectionQueue.sync {
            existingEntry = activeConnections[address]
        }

        if let entry = existingEntry, entry.connection.isAlive() {
            print("[Native:iOS] PrintingModule.getOrCreateConnection - PROCESS: Reusing existing connection for \(address)")
            connectionQueue.async(flags: .barrier) {
                self.activeConnections[address]?.lastUsed = Date()
            }
            return entry
        } else if let entry = existingEntry {
            // Dead connection - remove it
            print("[Native:iOS] PrintingModule.getOrCreateConnection - PROCESS: Removing dead connection for \(address)")
            connectionQueue.async(flags: .barrier) {
                self.activeConnections.removeValue(forKey: address)
            }
            entry.connection.close()
        }

        // Create new connection
        print("[Native:iOS] PrintingModule.getOrCreateConnection - PROCESS: Creating new connection for \(address)")

        do {
            let connection = try PrintConnectionFactory.create(address: address)

            // Try to connect with retry
            let connected = try await withTimeout(seconds: timeoutSec) {
                try await connection.connectWithRetry(
                    maxRetries: Self.maxRetries,
                    delayMs: Self.retryDelayMs
                )
            }

            if connected {
                print("[Native:iOS] PrintingModule.getOrCreateConnection - SUCCESS: Connected to \(address)")
                return ConnectionEntry(connection: connection)
            } else {
                print("[Native:iOS] PrintingModule.getOrCreateConnection - ERROR: Failed to connect to \(address)")
                connection.close()
                return nil
            }
        } catch {
            print("[Native:iOS] PrintingModule.getOrCreateConnection - ERROR: \(error)")
            throw error
        }
    }

    /**
     * Helper function for timeout
     */
    private func withTimeout<T>(
        seconds: TimeInterval,
        operation: @escaping () async throws -> T
    ) async throws -> T {
        try await withThrowingTaskGroup(of: T.self) { group in
            group.addTask {
                try await operation()
            }

            group.addTask {
                try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
                throw PrintError.connectionTimeout("", timeoutMs: Int(seconds * 1000))
            }

            let result = try await group.next()!
            group.cancelAll()
            return result
        }
    }

    /**
     * Start cleanup timer
     */
    private func startCleanupTimer() {
        cleanupTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { _ in
            self.cleanupIdleConnections()
        }
    }

    /**
     * Clean up idle connections
     */
    private func cleanupIdleConnections() {
        let now = Date()
        let idleThreshold = now.addingTimeInterval(-Self.connectionIdleTimeSec)

        connectionQueue.async(flags: .barrier) {
            for (address, entry) in self.activeConnections {
                if entry.lastUsed < idleThreshold {
                    print("[PrintingModule] Closing idle connection: \(address)")
                    entry.connection.close()
                    self.activeConnections.removeValue(forKey: address)
                }
            }
        }
    }

    /**
     * Disconnect specific printer or all printers
     */
    @objc func disconnect(address: String? = nil) {
        connectionQueue.async(flags: .barrier) {
            if let address = address {
                // Disconnect specific printer
                if let entry = self.activeConnections[address] {
                    print("[PrintingModule] Disconnecting printer: \(address)")
                    entry.connection.close()
                    self.activeConnections.removeValue(forKey: address)
                }
            } else {
                // Disconnect all
                print("[PrintingModule] Disconnecting all printers")
                for (_, entry) in self.activeConnections {
                    entry.connection.close()
                }
                self.activeConnections.removeAll()
            }
        }
    }

    private func calculateMinimumWaitTimeAfterSend(dataSize: Int, address: String) -> Int {
        // Detect connection type from address
        let connectionType = address.split(separator: ":").first?.lowercased() ?? ""

        // Transmission time (ms per byte)
        let transmissionMs: Int
        switch connectionType {
        case "ble":
            // BLE: chunks into 185-byte packets with delay
            // Empirical average: ~0.05ms per byte
            transmissionMs = Int(Double(dataSize) * 0.1)
        case "bt":
            // Bluetooth Classic: ~100KB/s = 0.01ms per byte
            transmissionMs = Int(Double(dataSize) * 0.01)
        case "lan":
            // LAN: ~1MB/s = 0.001ms per byte
            transmissionMs = Int(Double(dataSize) * 0.001)
        default:
            // Conservative default
            transmissionMs = Int(Double(dataSize) * 0.05)
        }

        // Processing time: Conservative estimates for all data types
        let processingMs: Int
        if dataSize < 1000 {
            processingMs = connectionType == "lan" ? 30 : 50  // Small data
        } else if dataSize < 10000 {
            processingMs = connectionType == "lan" ? 150 : 200  // Medium data
        } else {
            processingMs = connectionType == "lan" ? 400 : 500  // Large data
        }

        // Total = transmission + processing
        let totalMs = transmissionMs + processingMs
        let maxMs = connectionType == "lan" ? 3000 : 5000
        return min(totalMs, maxMs)
    }

    /**
     * Get final safety buffer before closing connection
     * Fixed delay added after waitTimeAfterSend to ensure printer completes processing
     */
    private func getFinalSafetyBufferBeforeClose(address: String) -> Int {
        let connectionType = address.split(separator: ":").first?.lowercased() ?? ""

        switch connectionType {
        case "ble":
            return 200  // BLE: Need extra time for chunking overhead
        case "bt":
            return 100  // Bluetooth Classic: Moderate buffer
        case "lan":
            return 50   // LAN: Very stable, minimal buffer
        default:
            return 100  // Conservative default
        }
    }

    /**
     * Clean up resources
     */
    @objc func cleanup() {
        cleanupTimer?.invalidate()
        cleanupTimer = nil
        disconnect() // Disconnect all
    }
}