import Foundation
import AVFoundation
import UIKit

@objc public class QRCodeScanner: NSObject {
    private var captureSession: AVCaptureSession?
    private var previewLayer: AVCaptureVideoPreviewLayer?
    private var scanCallback: ((String?, Error?) -> Void)?
    private var currentDevice: AVCaptureDevice?
    private var videoOutput: AVCaptureVideoDataOutput?
    
    // Scanner configuration
    private var scanFormats: [AVMetadataObject.ObjectType] = [.qr]
    private var torchEnabled: Bool = false
    private var zoomLevel: CGFloat = 1.0
    
    override init() {
        super.init()
    }
    
    // MARK: - Public Methods
    
    @objc public func startScanning(
        in view: UIView,
        formats: [String]? = nil,
        completion: @escaping (String?, Error?) -> Void
    ) {
        self.scanCallback = completion
        
        // Configure scan formats
        if let formats = formats {
            self.scanFormats = formats.compactMap { format in
                switch format {
                case "QR_CODE": return .qr
                case "AZTEC": return .aztec
                case "CODE_128": return .code128
                case "CODE_39": return .code39
                case "CODE_93": return .code93
                case "DATA_MATRIX": return .dataMatrix
                case "EAN_13": return .ean13
                case "EAN_8": return .ean8
                case "ITF": return .itf14
                case "PDF_417": return .pdf417
                case "UPC_A": return .upce
                case "UPC_E": return .upce
                default: return nil
                }
            }
        }
        
        // Check camera permission
        checkCameraPermission { [weak self] granted in
            if granted {
                DispatchQueue.main.async {
                    self?.setupCamera(in: view)
                }
            } else {
                completion(nil, QRScannerError.permissionDenied)
            }
        }
    }
    
    @objc public func stopScanning() {
        captureSession?.stopRunning()
        previewLayer?.removeFromSuperlayer()
        captureSession = nil
        previewLayer = nil
        scanCallback = nil
    }
    
    @objc public func pauseScanning() {
        captureSession?.stopRunning()
    }
    
    @objc public func resumeScanning() {
        if captureSession?.isRunning == false {
            captureSession?.startRunning()
        }
    }
    
    @objc public func toggleTorch() throws {
        guard let device = currentDevice, device.hasTorch else {
            throw QRScannerError.torchUnavailable
        }
        
        try device.lockForConfiguration()
        torchEnabled = !torchEnabled
        device.torchMode = torchEnabled ? .on : .off
        device.unlockForConfiguration()
    }
    
    @objc public func setZoom(level: CGFloat) throws {
        guard let device = currentDevice else {
            throw QRScannerError.cameraUnavailable
        }
        
        let zoom = max(1.0, min(level, device.activeFormat.videoMaxZoomFactor))
        
        try device.lockForConfiguration()
        device.videoZoomFactor = zoom
        self.zoomLevel = zoom
        device.unlockForConfiguration()
    }
    
    @objc public func flipCamera() throws {
        guard let session = captureSession else {
            throw QRScannerError.sessionNotRunning
        }
        
        session.beginConfiguration()
        
        // Remove existing input
        if let currentInput = session.inputs.first as? AVCaptureDeviceInput {
            session.removeInput(currentInput)
        }
        
        // Get the other camera
        let position: AVCaptureDevice.Position = currentDevice?.position == .back ? .front : .back
        
        guard let newDevice = AVCaptureDevice.default(
            .builtInWideAngleCamera,
            for: .video,
            position: position
        ) else {
            session.commitConfiguration()
            throw QRScannerError.cameraUnavailable
        }
        
        // Add new input
        do {
            let newInput = try AVCaptureDeviceInput(device: newDevice)
            if session.canAddInput(newInput) {
                session.addInput(newInput)
                currentDevice = newDevice
            }
        } catch {
            session.commitConfiguration()
            throw error
        }
        
        session.commitConfiguration()
    }
    
    @objc public func scanFromImage(_ image: UIImage, completion: @escaping (String?, Error?) -> Void) {
        guard let ciImage = CIImage(image: image) else {
            completion(nil, QRScannerError.invalidImage)
            return
        }
        
        let detector = CIDetector(
            ofType: CIDetectorTypeQRCode,
            context: nil,
            options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]
        )
        
        let features = detector?.features(in: ciImage) ?? []
        
        if let qrFeature = features.first as? CIQRCodeFeature,
           let messageString = qrFeature.messageString {
            completion(messageString, nil)
        } else {
            completion(nil, QRScannerError.noQRCodeFound)
        }
    }
    
    @objc public func scanFromImageWithDetails(_ image: UIImage, formats: [String], completion: @escaping ([[String: Any]]?, Error?) -> Void) {
        guard let ciImage = CIImage(image: image) else {
            completion(nil, QRScannerError.invalidImage)
            return
        }
        
        var results: [[String: Any]] = []
        
        // Try QR Code detector
        if formats.isEmpty || formats.contains("QR_CODE") {
            let qrDetector = CIDetector(
                ofType: CIDetectorTypeQRCode,
                context: nil,
                options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]
            )
            
            let qrFeatures = qrDetector?.features(in: ciImage) ?? []
            for feature in qrFeatures {
                if let qrFeature = feature as? CIQRCodeFeature,
                   let messageString = qrFeature.messageString {
                    var result: [String: Any] = [
                        "format": "QR_CODE",
                        "rawValue": messageString,
                        "displayValue": messageString
                    ]
                    
                    // Add bounds if available
                    let bounds = qrFeature.bounds
                    result["boundingBox"] = [
                        "left": bounds.origin.x,
                        "top": bounds.origin.y,
                        "right": bounds.origin.x + bounds.width,
                        "bottom": bounds.origin.y + bounds.height
                    ]
                    
                    results.append(result)
                }
            }
        }
        
        // For other barcode formats, we would need to use Vision framework
        // or a third-party library as CIDetector only supports QR codes
        
        if results.isEmpty {
            completion(nil, QRScannerError.noQRCodeFound)
        } else {
            completion(results, nil)
        }
    }
    
    @objc public var isTorchEnabled: Bool {
        return torchEnabled
    }
    
    @objc public var isTorchAvailable: Bool {
        return currentDevice?.hasTorch ?? false
    }
    
    @objc public func setZoom(_ zoom: CGFloat) {
        if let device = currentDevice {
            do {
                try device.lockForConfiguration()
                device.videoZoomFactor = max(1.0, min(zoom, device.activeFormat.videoMaxZoomFactor))
                device.unlockForConfiguration()
            } catch {
                // Handle error silently
            }
        }
    }
    
    // MARK: - Private Methods
    
    private func checkCameraPermission(completion: @escaping (Bool) -> Void) {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            completion(true)
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video) { granted in
                completion(granted)
            }
        case .denied, .restricted:
            completion(false)
        @unknown default:
            completion(false)
        }
    }
    
    private func setupCamera(in view: UIView) {
        captureSession = AVCaptureSession()
        
        guard let captureSession = captureSession else { return }
        
        // Get camera device
        guard let device = AVCaptureDevice.default(
            .builtInWideAngleCamera,
            for: .video,
            position: .back
        ) else {
            scanCallback?(nil, QRScannerError.cameraUnavailable)
            return
        }
        
        currentDevice = device
        
        do {
            // Configure input
            let input = try AVCaptureDeviceInput(device: device)
            if captureSession.canAddInput(input) {
                captureSession.addInput(input)
            }
            
            // Configure output
            let output = AVCaptureMetadataOutput()
            if captureSession.canAddOutput(output) {
                captureSession.addOutput(output)
                output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
                output.metadataObjectTypes = scanFormats
            }
            
            // Configure preview layer
            previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            previewLayer?.frame = view.bounds
            previewLayer?.videoGravity = .resizeAspectFill
            
            if let previewLayer = previewLayer {
                view.layer.addSublayer(previewLayer)
            }
            
            // Start session
            DispatchQueue.global(qos: .background).async {
                captureSession.startRunning()
            }
            
        } catch {
            scanCallback?(nil, error)
        }
    }
    
    // MARK: - Scanner Configuration
    
    @objc public func configureScanArea(rect: CGRect) {
        guard let output = captureSession?.outputs.first as? AVCaptureMetadataOutput,
              let previewLayer = previewLayer else { return }
        
        // Convert the rect to the metadata output coordinate system
        let rectOfInterest = previewLayer.metadataOutputRectConverted(fromLayerRect: rect)
        output.rectOfInterest = rectOfInterest
    }
    
    @objc public func setFocusMode(_ mode: String) throws {
        guard let device = currentDevice else {
            throw QRScannerError.cameraUnavailable
        }
        
        let focusMode: AVCaptureDevice.FocusMode
        switch mode {
        case "auto":
            focusMode = .autoFocus
        case "continuous":
            focusMode = .continuousAutoFocus
        case "locked":
            focusMode = .locked
        default:
            focusMode = .autoFocus
        }
        
        if device.isFocusModeSupported(focusMode) {
            try device.lockForConfiguration()
            device.focusMode = focusMode
            device.unlockForConfiguration()
        }
    }
}

// MARK: - AVCaptureMetadataOutputObjectsDelegate

extension QRCodeScanner: AVCaptureMetadataOutputObjectsDelegate {
    public func metadataOutput(
        _ output: AVCaptureMetadataOutput,
        didOutput metadataObjects: [AVMetadataObject],
        from connection: AVCaptureConnection
    ) {
        guard let metadataObject = metadataObjects.first,
              let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
              let stringValue = readableObject.stringValue else {
            return
        }
        
        // Vibrate on successful scan
        AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
        
        // Stop scanning to prevent multiple callbacks
        captureSession?.stopRunning()
        
        // Return the result
        scanCallback?(stringValue, nil)
    }
}

// MARK: - Error Types

enum QRScannerError: LocalizedError {
    case permissionDenied
    case cameraUnavailable
    case torchUnavailable
    case sessionNotRunning
    case invalidImage
    case noQRCodeFound
    
    var errorDescription: String? {
        switch self {
        case .permissionDenied:
            return "Camera permission denied"
        case .cameraUnavailable:
            return "Camera not available on this device"
        case .torchUnavailable:
            return "Torch not available on this device"
        case .sessionNotRunning:
            return "Scanner session is not running"
        case .invalidImage:
            return "Invalid image provided"
        case .noQRCodeFound:
            return "No QR code found in the image"
        }
    }
}