import Foundation
import UIKit
import CoreImage
import Metal
import AVFoundation
import ImageIO
import UniformTypeIdentifiers

/// Main engine for image editing operations
public class ImageEditorEngine {
    // Core components
    private let config: ImageEditorConfig
    private let ciContext: CIContext
    private let metalDevice: MTLDevice?

    // Managers
    public let layerManager: LayerManager
    public let historyManager: HistoryManager
    public let filterPipeline: ImageFilterPipeline
    private let memoryMonitor = MemoryMonitor.shared
    private var memoryObserverToken: UUID?

    // Current state
    private(set) public var sourceImage: CIImage?
    public var currentAdjustments = ImageAdjustments()

    // Memory management
    public var onMemoryWarning: ((MemoryPressureLevel) -> Void)?

    // MARK: - Initialization

    public init(config: ImageEditorConfig = ImageEditorConfig()) {
        self.config = config

        // Initialize Metal if available
        if config.useMetalAcceleration, let device = MTLCreateSystemDefaultDevice() {
            self.metalDevice = device
            self.ciContext = CIContext(mtlDevice: device, options: config.processingQuality.ciContextOptions)
        } else {
            self.metalDevice = nil
            self.ciContext = CIContext(options: config.processingQuality.ciContextOptions)
        }

        self.layerManager = LayerManager(ciContext: ciContext)
        self.historyManager = HistoryManager(maxHistoryStates: config.maxHistoryStates)
        self.filterPipeline = ImageFilterPipeline(ciContext: ciContext)

        // Setup memory monitoring
        setupMemoryMonitoring()
    }

    // MARK: - Image Loading

    /// Load an image from a file path
    public func loadImage(from path: String) throws -> UIImage {
        guard let url = URL(string: path) else {
            throw ImageEditorError.imageLoadFailed
        }

        return try loadImage(from: url)
    }

    /// Load an image from a URL
    public func loadImage(from url: URL) throws -> UIImage {
        guard let ciImage = CIImage(contentsOf: url) else {
            throw ImageEditorError.imageLoadFailed
        }

        self.sourceImage = ciImage

        // Create initial layer
        let initialLayer = ImageLayer(
            name: "Background",
            image: ciImage,
            bounds: ciImage.extent
        )
        layerManager.addLayer(initialLayer)

        // Save initial state
        saveCurrentState()

        return renderCurrentImage()
    }

    /// Load an image from UIImage
    public func loadImage(_ image: UIImage) {
        guard let ciImage = CIImage(image: image) else { return }

        self.sourceImage = ciImage

        let initialLayer = ImageLayer(
            name: "Background",
            image: ciImage,
            bounds: ciImage.extent
        )
        layerManager.addLayer(initialLayer)

        saveCurrentState()
    }

    // MARK: - Adjustments

    /// Apply adjustments to the current image
    public func applyAdjustments(_ adjustments: ImageAdjustments) -> UIImage {
        currentAdjustments = adjustments
        return renderCurrentImage()
    }

    /// Update a specific adjustment parameter
    public func updateAdjustment(_ keyPath: WritableKeyPath<ImageAdjustments, Float>, value: Float) -> UIImage {
        currentAdjustments[keyPath: keyPath] = value
        return renderCurrentImage()
    }

    // MARK: - Filters

    /// Apply a preset filter
    public func applyFilter(_ preset: FilterPreset, intensity: Float = 1.0) -> UIImage {
        guard let baseImage = layerManager.layers.first?.image else {
            return UIImage()
        }

        let filteredCIImage = filterPipeline.applyPresetFilter(preset, to: baseImage, intensity: intensity)

        // Update the base layer with the filtered CIImage
        if let firstLayer = layerManager.layers.first {
            firstLayer.image = filteredCIImage
        }

        saveCurrentState()
        return renderCurrentImage()
    }

    // MARK: - Transformations

    /// Crop the image to the specified rect
    public func crop(to rect: CGRect) throws -> UIImage {
        guard let baseImage = layerManager.layers.first?.image else {
            throw ImageEditorError.processingFailed
        }

        let croppedImage = baseImage.cropped(to: rect)

        // Update the base layer
        if let firstLayer = layerManager.layers.first {
            firstLayer.image = croppedImage
            firstLayer.bounds = rect
        }

        saveCurrentState()
        return renderCurrentImage()
    }

    /// Rotate the image by the specified angle (in radians)
    public func rotate(by angle: CGFloat) -> UIImage {
        guard let baseImage = layerManager.layers.first?.image else {
            return UIImage()
        }

        let transform = CGAffineTransform(rotationAngle: angle)
        let rotatedImage = baseImage.transformed(by: transform)

        if let firstLayer = layerManager.layers.first {
            firstLayer.image = rotatedImage
        }

        saveCurrentState()
        return renderCurrentImage()
    }

    /// Flip the image horizontally
    public func flipHorizontal() -> UIImage {
        guard let baseImage = layerManager.layers.first?.image else {
            return UIImage()
        }

        let transform = CGAffineTransform(scaleX: -1, y: 1)
            .translatedBy(x: -baseImage.extent.width, y: 0)
        let flippedImage = baseImage.transformed(by: transform)

        if let firstLayer = layerManager.layers.first {
            firstLayer.image = flippedImage
        }

        saveCurrentState()
        return renderCurrentImage()
    }

    /// Flip the image vertically
    public func flipVertical() -> UIImage {
        guard let baseImage = layerManager.layers.first?.image else {
            return UIImage()
        }

        let transform = CGAffineTransform(scaleX: 1, y: -1)
            .translatedBy(x: 0, y: -baseImage.extent.height)
        let flippedImage = baseImage.transformed(by: transform)

        if let firstLayer = layerManager.layers.first {
            firstLayer.image = flippedImage
        }

        saveCurrentState()
        return renderCurrentImage()
    }

    /// Resize the image to the specified size
    public func resize(to size: CGSize) -> UIImage {
        guard let baseImage = layerManager.layers.first?.image else {
            return UIImage()
        }

        let scaleX = size.width / baseImage.extent.width
        let scaleY = size.height / baseImage.extent.height
        let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
        let resizedImage = baseImage.transformed(by: transform)

        if let firstLayer = layerManager.layers.first {
            firstLayer.image = resizedImage
            firstLayer.bounds = CGRect(origin: .zero, size: size)
        }

        saveCurrentState()
        return renderCurrentImage()
    }

    // MARK: - Layer Operations

    /// Add a new layer with an image
    public func addLayer(name: String, image: UIImage) -> UUID {
        guard let ciImage = CIImage(image: image) else {
            return UUID()
        }

        let layer = ImageLayer(name: name, image: ciImage, bounds: ciImage.extent)
        layerManager.addLayer(layer)
        saveCurrentState()

        return layer.id
    }

    /// Update layer opacity
    public func updateLayerOpacity(layerId: UUID, opacity: Float) {
        guard let layer = layerManager.layer(withId: layerId) else { return }
        layer.opacity = opacity
    }

    /// Update layer blend mode
    public func updateLayerBlendMode(layerId: UUID, blendMode: ImageLayer.BlendMode) {
        guard let layer = layerManager.layer(withId: layerId) else { return }
        layer.blendMode = blendMode
    }

    /// Toggle layer visibility
    public func toggleLayerVisibility(layerId: UUID) {
        guard let layer = layerManager.layer(withId: layerId) else { return }
        layer.isVisible.toggle()
    }

    // MARK: - History

    /// Undo the last operation
    public func undo() -> UIImage? {
        let currentState = EditorState(
            layers: layerManager.snapshot(),
            adjustments: currentAdjustments.toDictionary()
        )

        guard let previousState = historyManager.undo(currentState: currentState) else {
            return nil
        }

        restoreState(previousState)
        return renderCurrentImage()
    }

    /// Redo the last undone operation
    public func redo() -> UIImage? {
        let currentState = EditorState(
            layers: layerManager.snapshot(),
            adjustments: currentAdjustments.toDictionary()
        )

        guard let nextState = historyManager.redo(currentState: currentState) else {
            return nil
        }

        restoreState(nextState)
        return renderCurrentImage()
    }

    // MARK: - Export

    /// Export options for more control
    public struct ExportOptions {
        public var format: ExportFormat = .jpeg
        public var quality: Float = 0.9  // For JPEG and HEIC (0.0 - 1.0)
        public var maxDimension: Int? = nil  // For downscaling (nil = full resolution)
        public var progressCallback: ((Double) -> Void)? = nil

        public init(format: ExportFormat = .jpeg, quality: Float = 0.9, maxDimension: Int? = nil, progressCallback: ((Double) -> Void)? = nil) {
            self.format = format
            self.quality = quality
            self.maxDimension = maxDimension
            self.progressCallback = progressCallback
        }
    }

    /// Export the current image with options
    public func export(options: ExportOptions = ExportOptions()) throws -> Data {
        let image = renderCurrentImage()

        guard let cgImage = image.cgImage else {
            throw ImageEditorError.exportFailed
        }

        // Downscale if requested
        let finalImage: CGImage
        if let maxDim = options.maxDimension {
            finalImage = try downscaleImage(cgImage, maxDimension: maxDim)
        } else {
            finalImage = cgImage
        }

        options.progressCallback?(0.5)

        switch options.format {
        case .jpeg:
            guard let data = exportAsJPEG(finalImage, quality: options.quality) else {
                throw ImageEditorError.exportFailed
            }
            options.progressCallback?(1.0)
            return data

        case .png:
            guard let data = exportAsPNG(finalImage) else {
                throw ImageEditorError.exportFailed
            }
            options.progressCallback?(1.0)
            return data

        case .heic:
            guard let data = try exportAsHEIC(finalImage, quality: options.quality) else {
                throw ImageEditorError.exportFailed
            }
            options.progressCallback?(1.0)
            return data
        }
    }

    /// Legacy export method for backward compatibility
    public func export(format: ExportFormat = .jpeg, quality: Float = 0.9) throws -> Data {
        return try export(options: ExportOptions(format: format, quality: quality))
    }

    /// Export to file
    public func exportToFile(url: URL, format: ExportFormat = .jpeg, quality: Float = 0.9) throws {
        let data = try export(format: format, quality: quality)
        try data.write(to: url)
    }

    // MARK: - Export Helpers

    private func exportAsJPEG(_ cgImage: CGImage, quality: Float) -> Data? {
        return UIImage(cgImage: cgImage).jpegData(compressionQuality: CGFloat(quality))
    }

    private func exportAsPNG(_ cgImage: CGImage) -> Data? {
        return UIImage(cgImage: cgImage).pngData()
    }

    private func exportAsHEIC(_ cgImage: CGImage, quality: Float) throws -> Data? {
        // Create destination data
        let data = NSMutableData()

        // Create image destination
        guard let destination = CGImageDestinationCreateWithData(
            data as CFMutableData,
            UTType.heic.identifier as CFString,
            1,
            nil
        ) else {
            throw ImageEditorError.exportFailed
        }

        // Set compression quality
        let options: [CFString: Any] = [
            kCGImageDestinationLossyCompressionQuality: CGFloat(quality)
        ]

        // Add image to destination
        CGImageDestinationAddImage(destination, cgImage, options as CFDictionary)

        // Finalize
        guard CGImageDestinationFinalize(destination) else {
            throw ImageEditorError.exportFailed
        }

        return data as Data
    }

    private func downscaleImage(_ cgImage: CGImage, maxDimension: Int) throws -> CGImage {
        let width = cgImage.width
        let height = cgImage.height
        let maxDim = max(width, height)

        // No need to downscale
        if maxDim <= maxDimension {
            return cgImage
        }

        // Calculate new size
        let scale = CGFloat(maxDimension) / CGFloat(maxDim)
        let newWidth = Int(CGFloat(width) * scale)
        let newHeight = Int(CGFloat(height) * scale)

        // Create CI image and scale
        let ciImage = CIImage(cgImage: cgImage)
        let scaleFilter = CIFilter(name: "CILanczosScaleTransform")!
        scaleFilter.setValue(ciImage, forKey: kCIInputImageKey)
        scaleFilter.setValue(scale, forKey: kCIInputScaleKey)
        scaleFilter.setValue(1.0, forKey: kCIInputAspectRatioKey)

        guard let outputImage = scaleFilter.outputImage,
              let scaledCGImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else {
            throw ImageEditorError.exportFailed
        }

        return scaledCGImage
    }

    // MARK: - Private Methods

    private func renderCurrentImage() -> UIImage {
        guard let compositeImage = layerManager.composite() else {
            return UIImage()
        }

        // Apply adjustments
        let adjustedImage = filterPipeline.applyAdjustments(currentAdjustments, to: compositeImage)

        // Render to UIImage
        guard let cgImage = ciContext.createCGImage(adjustedImage, from: adjustedImage.extent) else {
            return UIImage()
        }

        return UIImage(cgImage: cgImage)
    }

    private func saveCurrentState() {
        let state = EditorState(
            layers: layerManager.snapshot(),
            adjustments: currentAdjustments.toDictionary()
        )
        historyManager.saveState(state)
    }

    private func restoreState(_ state: EditorState) {
        layerManager.restore(from: state.layers)
        currentAdjustments = ImageAdjustments.fromDictionary(state.adjustments)
    }

    // MARK: - State Management

    /// Create current editor state snapshot for history
    public func createEditorState() -> EditorState {
        return EditorState(
            layers: layerManager.layers,
            adjustments: currentAdjustments.toDictionary(),
            timestamp: Date()
        )
    }

    // MARK: - Memory Management

    private func setupMemoryMonitoring() {
        memoryObserverToken = memoryMonitor.addObserver { [weak self] level in
            guard let self = self else { return }

            // Call user callback if set
            self.onMemoryWarning?(level)

            // Automatic cache management
            switch level {
            case .warning:
                // Clear filter cache on warning
                self.filterPipeline.clearLUTCache()
            case .critical:
                // More aggressive cleanup on critical
                self.filterPipeline.clearLUTCache()
                // Could also reduce history depth or clear some layers
            case .normal:
                break
            }
        }
    }

    /// Get current memory statistics
    public func getMemoryStats() -> MemoryStats {
        return memoryMonitor.getMemoryStats()
    }

    /// Check if operation is safe to perform given memory constraints
    public func canPerformOperation(estimatedMemoryMB: Double) -> Bool {
        return memoryMonitor.canPerformOperation(requiredMB: estimatedMemoryMB)
    }

    /// Get recommended history depth for current image
    public func getRecommendedHistoryDepth() -> Int {
        guard let image = sourceImage else {
            return historyManager.maxHistoryStates
        }

        let width = Int(image.extent.width)
        let height = Int(image.extent.height)

        return memoryMonitor.adaptiveHistoryDepth(
            imageWidth: width,
            imageHeight: height,
            defaultDepth: config.maxHistoryStates
        )
    }

    deinit {
        if let token = memoryObserverToken {
            memoryMonitor.removeObserver(token)
        }
    }
}

// MARK: - Export Format

public enum ExportFormat: String {
    case jpeg
    case png
    case heic
}
