//
//  LiveScanner.swift
//  DWP
//
//  Created by Ahmed Wahdan on 19/12/2024.
//

import UIKit
import AVFoundation
import Vision

@available(iOS 13, *)
class LiveScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
  private var captureSession: AVCaptureSession?
  private var previewLayer: AVCaptureVideoPreviewLayer?
  private var onCardScanned: (([String: String]) -> Void)?
  private(set) var finalCardDetails: [String: String] = [:]
  private var cardFrame: CGRect?
  private var isScanning = false
  private var frameCounter = 0
  private let frameProcessingInterval = 5
  private var lastCapturedImage: UIImage?
  
  
  private let validator = CardValidation.shared
  private let imageProcessor = ImageProcessor.shared
  private let uiManager = LiveScannerUI()
  private var scannerFrame: ScannerView = ScannerView()
  
  
  func startScanning(
    in parentView: UIView,
    cardFrame: CGRect,
    completion: @escaping ([String: String]) -> Void
  ) {
    DispatchQueue.main.async {
      self.scannerFrame.removeFromSuperview()
      self.scannerFrame = ScannerView(frame: parentView.bounds)
      self.scannerFrame.backgroundColor = .clear
      parentView.addSubview(self.scannerFrame)
      self.scannerFrame.startScanAnimation()
      self.uiManager.setupCardDataLabels(in: parentView)
    }
    guard !isScanning else { return }
    isScanning = true
    self.cardFrame = cardFrame
    self.onCardScanned = completion
    configureScanner(in: parentView)
  }
  
  func stopScanning(completion: (() -> Void)? = nil) {
    DispatchQueue.main.async {
      self.uiManager.resetLabels() // Reset the UI labels
    }
    guard isScanning else { return }
    isScanning = false
    
    DispatchQueue.global(qos: .background).async { [weak self] in
      guard let self = self else { return }
      
      // Stop the capture session
      if let session = self.captureSession, session.isRunning {
        session.stopRunning()
        self.captureSession = nil
      }
      
      DispatchQueue.main.async {
        // Remove the preview layer
        self.previewLayer?.removeFromSuperlayer()
        self.previewLayer = nil
        
        // Handle last captured image
        if let lastCapturedImage = self.lastCapturedImage {
          self.uiManager.displayLastCapturedImage(lastCapturedImage)
        } else {
          print("No image available to display")
        }
        
        // Stop the scanner frame animation
        self.scannerFrame.stopScanAnimation()
        
        // Optional completion callback
        completion?()
      }
    }
  }
  
  
  private func configureScanner(in parentView: UIView) {
    guard let captureDevice = AVCaptureDevice.default(for: .video) else {
      DispatchQueue.main.async {
        self.onCardScanned?(["error": "Camera not supported"])
      }
      return
    }
    
    do {
      // Configure camera
      try captureDevice.lockForConfiguration()
      if captureDevice.isFocusModeSupported(.continuousAutoFocus) {
        captureDevice.focusMode = .continuousAutoFocus
      }
      if captureDevice.isExposureModeSupported(.continuousAutoExposure) {
        captureDevice.exposureMode = .continuousAutoExposure
      }
      captureDevice.unlockForConfiguration()
      
      let input = try AVCaptureDeviceInput(device: captureDevice)
      let session = AVCaptureSession()
      session.addInput(input)
      
      let videoOutput = AVCaptureVideoDataOutput()
      videoOutput.alwaysDiscardsLateVideoFrames = true
      videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
      session.addOutput(videoOutput)
      
      self.captureSession = session
      
      // Add preview layer
      previewLayer = AVCaptureVideoPreviewLayer(session: session)
      previewLayer?.videoGravity = .resizeAspectFill
      previewLayer?.frame = parentView.bounds
      parentView.layer.addSublayer(previewLayer!)
      
      // Start session
      DispatchQueue.global(qos: .userInitiated).async {
        self.captureSession?.startRunning()
      }
    } catch {
      self.scannerFrame.stopScanAnimation()
      DispatchQueue.main.async {
        self.onCardScanned?(["error": "Failed to access camera"])
      }
    }
  }
  
  func captureOutput(
    _ output: AVCaptureOutput,
    didOutput sampleBuffer: CMSampleBuffer,
    from connection: AVCaptureConnection
  ) {
    frameCounter += 1
    if frameCounter % frameProcessingInterval != 0 { return }
    
    guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
    let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
    
    if let preprocessedImage = imageProcessor.preprocessImage(ciImage) {
      lastCapturedImage = UIImage(ciImage: ciImage, scale: 1.0, orientation: .right)
      performVisionRequest(on: preprocessedImage)
    }
  }
  
  private func performVisionRequest(on image: UIImage) {
    guard let ciImage = CIImage(image: image) else { return }
    let requestHandler = VNImageRequestHandler(ciImage: ciImage, options: [:])
    
    let request = VNRecognizeTextRequest { [weak self] request, error in
      guard let self = self else { return }
      if let error = error {
        print("Vision request error: \(error.localizedDescription)")
        return
      }
      
      guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
      var recognizedTexts = observations.compactMap { $0.topCandidates(1).first?.string }
      recognizedTexts = self.validator.filterRelevantTexts(recognizedTexts)
      
      // Extract card details from recognized texts
      let cardDetails = self.extractCardDetails(from: recognizedTexts)
      self.finalCardDetails = cardDetails
      
      // Live updates to the UI for card details
      DispatchQueue.main.async {
        self.uiManager.updateCardDataLabels(
          cardNumber: cardDetails["cardNumber"],
          expiryDate: cardDetails["expiryDate"],
          cardholderName: cardDetails["cardholderName"],
          cardType: cardDetails["cardType"]
        )
      }
      
      // If complete card details are found, stop scanning and return
      if cardDetails["cardNumber"] != nil, cardDetails["expiryDate"] != nil {
        self.stopScanning()
        DispatchQueue.main.async {
          self.onCardScanned?(cardDetails)
        }
      }
    }
    
    request.recognitionLevel = .accurate
    request.usesLanguageCorrection = false
    
    do {
      try requestHandler.perform([request])
    } catch {
      print("Failed to perform Vision request: \(error.localizedDescription)")
    }
  }
  
  
  private func extractCardDetails(from texts: [String]) -> [String: String] {
    var cardDetails: [String: String] = [:]
    
    if let cardNumber = texts.first(where: { validator.isValidCardNumber($0) }) {
      cardDetails["cardNumber"] = cardNumber
      cardDetails["cardType"] = validator.getCardType(for: cardNumber)
    }
    
    if let expiryDate = texts.first(where: { validator.extractExpiryDate(from: $0) != nil }) {
      cardDetails["expiryDate"] = validator.extractExpiryDate(from: expiryDate)
    }
    
    if let cardholderName = texts.first(where: { validator.isValidName($0) }) {
      cardDetails["cardholderName"] = validator.normalizeName(cardholderName)
    }
    
    return cardDetails
  }
}
