import AVFoundation
import Combine
import OSLog
import UIKit

struct CameraSessionState {
    private(set) var configurationDepth = 0

    var canStartSession: Bool {
        configurationDepth == 0
    }

    mutating func beginConfiguration() {
        configurationDepth += 1
    }

    mutating func commitConfiguration() {
        configurationDepth = max(0, configurationDepth - 1)
    }
}

enum CameraServiceError: LocalizedError {
    case unauthorized
    case unavailable
    case captureFailed

    var errorDescription: String? {
        switch self {
        case .unauthorized:
            return "Camera access is required to capture a card photo."
        case .unavailable:
            return "The device camera is unavailable."
        case .captureFailed:
            return "The photo could not be captured."
        }
    }
}

@MainActor
final class CameraService: NSObject, ObservableObject {
    private static let logger = Logger(subsystem: "dev.matthiasmeister.StackDex", category: "CameraService")

    @Published private(set) var authorizationStatus: AVAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
    @Published private(set) var isSessionRunning = false
    @Published private(set) var isConfigured = false

    let session = AVCaptureSession()

    private let photoOutput = AVCapturePhotoOutput()
    private var captureContinuation: CheckedContinuation<UIImage, Error>?
    private var sessionState = CameraSessionState()

    func prepareIfAuthorized() async {
        authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
        guard authorizationStatus == .authorized else { return }
        await configureAndStartSessionIfNeeded()
    }

    func requestAccessAndStart() async {
        authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)

        if authorizationStatus == .notDetermined {
            authorizationStatus = await AVCaptureDevice.requestAccess(for: .video) ? .authorized : .denied
        }

        guard authorizationStatus == .authorized else { return }
        await configureAndStartSessionIfNeeded()
    }

    func stopSession() {
        if session.isRunning {
            session.stopRunning()
            isSessionRunning = false
        }
    }

    func capturePhoto() async throws -> UIImage {
        guard authorizationStatus == .authorized else {
            throw CameraServiceError.unauthorized
        }

        if !isSessionRunning {
            await configureAndStartSessionIfNeeded()
        }

        return try await withCheckedThrowingContinuation { continuation in
            captureContinuation = continuation
            photoOutput.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
        }
    }

    private func configureAndStartSessionIfNeeded() async {
        if isConfigured {
            startSessionIfNeeded()
            return
        }

        var shouldStartSession = false

        sessionState.beginConfiguration()
        Self.logger.debug("Camera session beginConfiguration depth=\(self.sessionState.configurationDepth)")
        session.beginConfiguration()

        do {
            session.sessionPreset = .photo

            guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
                throw CameraServiceError.unavailable
            }

            let input = try AVCaptureDeviceInput(device: device)

            if !session.inputs.contains(where: { ($0 as? AVCaptureDeviceInput)?.device == device }) && session.canAddInput(input) {
                session.addInput(input)
            }

            if !session.outputs.contains(photoOutput) && session.canAddOutput(photoOutput) {
                session.addOutput(photoOutput)
            }

            isConfigured = true
            shouldStartSession = true
        } catch {
            isConfigured = false
            Self.logger.error("Camera session configuration failed: \(String(describing: error), privacy: .public)")
        }

        session.commitConfiguration()
        sessionState.commitConfiguration()
        Self.logger.debug("Camera session commitConfiguration depth=\(self.sessionState.configurationDepth)")

        if shouldStartSession {
            startSessionIfNeeded()
        }
    }

    private func startSessionIfNeeded() {
        guard sessionState.canStartSession else {
            let message = "Attempted to start AVCaptureSession before commitConfiguration completed."
            Self.logger.fault("\(message, privacy: .public)")
            assertionFailure(message)
            return
        }

        guard !session.isRunning else { return }

        Self.logger.debug("Starting camera session")
        session.startRunning()
        isSessionRunning = true
    }
}

extension CameraService: AVCapturePhotoCaptureDelegate {
    nonisolated func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        Task { @MainActor in
            if let error {
                captureContinuation?.resume(throwing: error)
                captureContinuation = nil
                return
            }

            guard let data = photo.fileDataRepresentation(), let image = UIImage(data: data) else {
                captureContinuation?.resume(throwing: CameraServiceError.captureFailed)
                captureContinuation = nil
                return
            }

            captureContinuation?.resume(returning: image)
            captureContinuation = nil
        }
    }
}
