import Foundation
import UIKit
import CoreImage
import Metal

/// Filter pipeline for applying image adjustments and effects
public class ImageFilterPipeline: MemoryAwareCache {
    private let ciContext: CIContext
    private var filterCache: [String: CIFilter] = [:]
    private var lutCache: LUTCache
    private let cacheQueue = DispatchQueue(label: "com.imageSDK.filtercache", attributes: .concurrent)
    private let maxCacheSize = 20  // Maximum number of cached filters

    public init(ciContext: CIContext) {
        self.ciContext = ciContext
        self.lutCache = LUTCache(maxCacheSize: 10)
    }

    /// Clear filter cache (for memory management)
    public func clearFilterCache() {
        cacheQueue.async(flags: .barrier) {
            self.filterCache.removeAll()
        }
    }

    // MARK: - MemoryAwareCache Protocol

    public func clearCacheOnMemoryPressure(level: MemoryPressureLevel) {
        switch level {
        case .warning:
            // Clear half the filter cache
            cacheQueue.async(flags: .barrier) {
                let halfSize = self.filterCache.count / 2
                let keysToRemove = Array(self.filterCache.keys.prefix(halfSize))
                keysToRemove.forEach { self.filterCache.removeValue(forKey: $0) }
            }
            // Clear LUT cache
            lutCache.clearAll()
        case .critical:
            // Clear everything
            clearFilterCache()
            lutCache.clearAll()
        case .normal:
            break
        }
    }

    /// Apply adjustments to an image
    public func applyAdjustments(_ adjustments: ImageAdjustments, to image: CIImage) -> CIImage {
        var outputImage = image

        // Brightness
        if adjustments.brightness != 0 {
            outputImage = applyFilter("CIColorControls", to: outputImage, parameters: [
                kCIInputBrightnessKey: adjustments.brightness
            ])
        }

        // Contrast
        if adjustments.contrast != 1.0 {
            outputImage = applyFilter("CIColorControls", to: outputImage, parameters: [
                kCIInputContrastKey: adjustments.contrast
            ])
        }

        // Saturation
        if adjustments.saturation != 1.0 {
            outputImage = applyFilter("CIColorControls", to: outputImage, parameters: [
                kCIInputSaturationKey: adjustments.saturation
            ])
        }

        // Exposure
        if adjustments.exposure != 0 {
            outputImage = applyFilter("CIExposureAdjust", to: outputImage, parameters: [
                kCIInputEVKey: adjustments.exposure
            ])
        }

        // Highlights & Shadows
        if adjustments.highlights != 1.0 || adjustments.shadows != 0 {
            outputImage = applyFilter("CIHighlightShadowAdjust", to: outputImage, parameters: [
                "inputHighlightAmount": adjustments.highlights,
                "inputShadowAmount": adjustments.shadows
            ])
        }

        // Temperature & Tint
        if adjustments.temperature != 0 || adjustments.tint != 0 {
            outputImage = applyFilter("CITemperatureAndTint", to: outputImage, parameters: [
                "inputNeutral": CIVector(x: 6500 + CGFloat(adjustments.temperature * 1000), y: CGFloat(adjustments.tint)),
                "inputTargetNeutral": CIVector(x: 6500, y: 0)
            ])
        }

        // Sharpness
        if adjustments.sharpness > 0 {
            outputImage = applyFilter("CISharpenLuminance", to: outputImage, parameters: [
                kCIInputSharpnessKey: adjustments.sharpness
            ])
        }

        // Noise Reduction
        if adjustments.noiseReduction > 0 {
            outputImage = applyFilter("CINoiseReduction", to: outputImage, parameters: [
                "inputNoiseLevel": adjustments.noiseReduction,
                kCIInputSharpnessKey: 0.4
            ])
        }

        // Vignette
        if adjustments.vignette > 0 {
            outputImage = applyFilter("CIVignette", to: outputImage, parameters: [
                kCIInputIntensityKey: adjustments.vignette,
                kCIInputRadiusKey: 1.5
            ])
        }

        return outputImage
    }

    /// Apply a preset filter
    public func applyPresetFilter(_ preset: FilterPreset, to image: CIImage, intensity: Float = 1.0) -> CIImage {
        var outputImage = image

        switch preset {
        case .vintage:
            outputImage = applyVintageFilter(to: outputImage, intensity: intensity)
        case .dramatic:
            outputImage = applyDramaticFilter(to: outputImage, intensity: intensity)
        case .mono:
            outputImage = applyMonoFilter(to: outputImage, intensity: intensity)
        case .noir:
            outputImage = applyNoirFilter(to: outputImage, intensity: intensity)
        case .fade:
            outputImage = applyFadeFilter(to: outputImage, intensity: intensity)
        case .chrome:
            outputImage = applyChromeFilter(to: outputImage, intensity: intensity)
        case .process:
            outputImage = applyProcessFilter(to: outputImage, intensity: intensity)
        case .transfer:
            outputImage = applyTransferFilter(to: outputImage, intensity: intensity)
        case .instant:
            outputImage = applyInstantFilter(to: outputImage, intensity: intensity)
        case .warm:
            outputImage = applyWarmFilter(to: outputImage, intensity: intensity)
        case .cool:
            outputImage = applyCoolFilter(to: outputImage, intensity: intensity)
        }

        // Blend with original based on intensity
        if intensity < 1.0 {
            outputImage = blendImages(original: image, filtered: outputImage, intensity: intensity)
        }

        return outputImage
    }

    /// Apply a LUT (Color Lookup Table)
    public func applyLUT(lutImage: CIImage, to image: CIImage, intensity: Float = 1.0) -> CIImage {
        guard let filter = CIFilter(name: "CIColorCubeWithColorSpace") else {
            return image
        }

        // This is a simplified version - proper LUT implementation requires 3D cube data
        filter.setValue(image, forKey: kCIInputImageKey)

        let outputImage = filter.outputImage ?? image

        if intensity < 1.0 {
            return blendImages(original: image, filtered: outputImage, intensity: intensity)
        }

        return outputImage
    }

    // MARK: - Private Filter Implementations

    private func applyVintageFilter(to image: CIImage, intensity: Float) -> CIImage {
        var output = image
        output = applyFilter("CIPhotoEffectTransfer", to: output, parameters: [:])
        output = applyFilter("CIVignette", to: output, parameters: [
            kCIInputIntensityKey: 0.8 * intensity,
            kCIInputRadiusKey: 1.5
        ])
        return output
    }

    private func applyDramaticFilter(to image: CIImage, intensity: Float) -> CIImage {
        var output = image
        output = applyFilter("CIColorControls", to: output, parameters: [
            kCIInputContrastKey: 1.3 * intensity,
            kCIInputSaturationKey: 1.2 * intensity
        ])
        return output
    }

    private func applyMonoFilter(to image: CIImage, intensity: Float) -> CIImage {
        return applyFilter("CIPhotoEffectMono", to: image, parameters: [:])
    }

    private func applyNoirFilter(to image: CIImage, intensity: Float) -> CIImage {
        return applyFilter("CIPhotoEffectNoir", to: image, parameters: [:])
    }

    private func applyFadeFilter(to image: CIImage, intensity: Float) -> CIImage {
        return applyFilter("CIPhotoEffectFade", to: image, parameters: [:])
    }

    private func applyChromeFilter(to image: CIImage, intensity: Float) -> CIImage {
        return applyFilter("CIPhotoEffectChrome", to: image, parameters: [:])
    }

    private func applyProcessFilter(to image: CIImage, intensity: Float) -> CIImage {
        return applyFilter("CIPhotoEffectProcess", to: image, parameters: [:])
    }

    private func applyTransferFilter(to image: CIImage, intensity: Float) -> CIImage {
        return applyFilter("CIPhotoEffectTransfer", to: image, parameters: [:])
    }

    private func applyInstantFilter(to image: CIImage, intensity: Float) -> CIImage {
        return applyFilter("CIPhotoEffectInstant", to: image, parameters: [:])
    }

    private func applyWarmFilter(to image: CIImage, intensity: Float) -> CIImage {
        var output = image
        output = applyFilter("CITemperatureAndTint", to: output, parameters: [
            "inputNeutral": CIVector(x: 7000, y: 0),
            "inputTargetNeutral": CIVector(x: 6500, y: 0)
        ])
        return output
    }

    private func applyCoolFilter(to image: CIImage, intensity: Float) -> CIImage {
        var output = image
        output = applyFilter("CITemperatureAndTint", to: output, parameters: [
            "inputNeutral": CIVector(x: 6000, y: 0),
            "inputTargetNeutral": CIVector(x: 6500, y: 0)
        ])
        return output
    }

    // MARK: - Helper Methods

    private func applyFilter(_ name: String, to image: CIImage, parameters: [String: Any]) -> CIImage {
        // Try to get from cache
        let filter: CIFilter
        if let cached = getCachedFilter(name) {
            filter = cached
        } else {
            guard let newFilter = CIFilter(name: name) else {
                return image
            }
            cacheFilter(name, filter: newFilter)
            filter = newFilter
        }

        filter.setValue(image, forKey: kCIInputImageKey)

        for (key, value) in parameters {
            filter.setValue(value, forKey: key)
        }

        return filter.outputImage ?? image
    }

    private func getCachedFilter(_ name: String) -> CIFilter? {
        return cacheQueue.sync {
            filterCache[name]
        }
    }

    private func cacheFilter(_ name: String, filter: CIFilter) {
        cacheQueue.async(flags: .barrier) {
            // LRU eviction if cache is full
            if self.filterCache.count >= self.maxCacheSize {
                // Remove first (oldest) entry
                if let firstKey = self.filterCache.keys.first {
                    self.filterCache.removeValue(forKey: firstKey)
                }
            }
            self.filterCache[name] = filter
        }
    }

    private func blendImages(original: CIImage, filtered: CIImage, intensity: Float) -> CIImage {
        guard let filter = CIFilter(name: "CIBlendWithAlphaMask") else {
            return filtered
        }

        // Create alpha mask based on intensity
        let maskImage = CIImage(color: CIColor(red: 1, green: 1, blue: 1, alpha: CGFloat(intensity)))
            .cropped(to: original.extent)

        filter.setValue(filtered, forKey: kCIInputImageKey)
        filter.setValue(original, forKey: kCIInputBackgroundImageKey)
        filter.setValue(maskImage, forKey: kCIInputMaskImageKey)

        return filter.outputImage ?? filtered
    }
}

// MARK: - Data Models

/// Adjustment parameters for basic image corrections
public struct ImageAdjustments {
    public var brightness: Float = 0.0        // -1.0 to 1.0
    public var contrast: Float = 1.0          // 0.0 to 2.0
    public var saturation: Float = 1.0        // 0.0 to 2.0
    public var exposure: Float = 0.0          // -2.0 to 2.0
    public var highlights: Float = 1.0        // 0.0 to 1.0
    public var shadows: Float = 0.0           // -1.0 to 1.0
    public var temperature: Float = 0.0       // -1.0 to 1.0
    public var tint: Float = 0.0              // -150 to 150
    public var sharpness: Float = 0.0         // 0.0 to 2.0
    public var noiseReduction: Float = 0.0    // 0.0 to 1.0
    public var vignette: Float = 0.0          // 0.0 to 1.0

    public init() {}

    public func toDictionary() -> [String: Float] {
        return [
            "brightness": brightness,
            "contrast": contrast,
            "saturation": saturation,
            "exposure": exposure,
            "highlights": highlights,
            "shadows": shadows,
            "temperature": temperature,
            "tint": tint,
            "sharpness": sharpness,
            "noiseReduction": noiseReduction,
            "vignette": vignette
        ]
    }

    public static func fromDictionary(_ dict: [String: Any]) -> ImageAdjustments {
        var adjustments = ImageAdjustments()
        adjustments.brightness = dict["brightness"] as? Float ?? 0.0
        adjustments.contrast = dict["contrast"] as? Float ?? 1.0
        adjustments.saturation = dict["saturation"] as? Float ?? 1.0
        adjustments.exposure = dict["exposure"] as? Float ?? 0.0
        adjustments.highlights = dict["highlights"] as? Float ?? 1.0
        adjustments.shadows = dict["shadows"] as? Float ?? 0.0
        adjustments.temperature = dict["temperature"] as? Float ?? 0.0
        adjustments.tint = dict["tint"] as? Float ?? 0.0
        adjustments.sharpness = dict["sharpness"] as? Float ?? 0.0
        adjustments.noiseReduction = dict["noiseReduction"] as? Float ?? 0.0
        adjustments.vignette = dict["vignette"] as? Float ?? 0.0
        return adjustments
    }
}

/// Preset filter types
public enum FilterPreset: String, CaseIterable {
    case vintage
    case dramatic
    case mono
    case noir
    case fade
    case chrome
    case process
    case transfer
    case instant
    case warm
    case cool
}

// MARK: - LUT Support

extension ImageFilterPipeline {

    /// Load a LUT from file for later use
    /// - Parameters:
    ///   - name: Unique identifier for the LUT
    ///   - fileURL: URL to .cube file
    /// - Throws: LUTParserError or LUTFilterError
    public func loadLUT(name: String, fileURL: URL) throws {
        _ = try lutCache.loadLUT(name: name, fileURL: fileURL, ciContext: ciContext)
    }

    /// Apply a LUT to an image
    /// - Parameters:
    ///   - name: Name of previously loaded LUT
    ///   - image: Input image
    ///   - intensity: Blend intensity (0.0 - 1.0)
    /// - Returns: Filtered image
    /// - Throws: LUTFilterError if LUT not found or application fails
    public func applyLUT(named name: String, to image: CIImage, intensity: Float = 1.0) throws -> CIImage {
        guard let lutFilter = lutCache.getLUT(name: name) else {
            throw LUTFilterError.invalidLUTData
        }

        return try lutFilter.apply(to: image, intensity: intensity)
    }

    /// Load and apply a LUT in one call
    /// - Parameters:
    ///   - fileURL: URL to .cube file
    ///   - image: Input image
    ///   - intensity: Blend intensity (0.0 - 1.0)
    /// - Returns: Filtered image
    /// - Throws: LUTParserError or LUTFilterError
    public func applyLUT(fileURL: URL, to image: CIImage, intensity: Float = 1.0) throws -> CIImage {
        let lutFilter = try LUTFilter(fileURL: fileURL, ciContext: ciContext)
        return try lutFilter.apply(to: image, intensity: intensity)
    }

    /// Remove a LUT from cache
    /// - Parameter name: Name of LUT to remove
    public func removeLUT(named name: String) {
        lutCache.removeLUT(name: name)
    }

    /// Clear all cached LUTs
    public func clearLUTCache() {
        lutCache.clearAll()
    }

    /// Get list of cached LUT names
    public var cachedLUTCount: Int {
        return lutCache.count
    }
}
