using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using Emgu.CV;
using Emgu.CV.Structure;
using ImageProcessing.Core;
using ImageProcessing.Processors;
using Serilog;

namespace XplorePlane.Services
{
    public class ImageProcessingService : IImageProcessingService
    {
        private readonly ILogger _logger;
        private readonly ConcurrentDictionary<string, ImageProcessorBase> _processorRegistry;
        private readonly ConcurrentDictionary<string, ImageProcessorBase16> _processorRegistry16;

        public ImageProcessingService(ILogger logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
            _processorRegistry = new ConcurrentDictionary<string, ImageProcessorBase>();
            _processorRegistry16 = new ConcurrentDictionary<string, ImageProcessorBase16>();
            RegisterBuiltInProcessors();
        }

        private void RegisterBuiltInProcessors()
        {
            // 8-bit processors
            _processorRegistry["GaussianBlur"] = new GaussianBlurProcessor();
            _processorRegistry["Threshold"] = new ThresholdProcessor();
            _processorRegistry["Division"] = new DivisionProcessor();
            _processorRegistry["Contrast"] = new ContrastProcessor();
            _processorRegistry["Gamma"] = new GammaProcessor();
            _processorRegistry["Morphology"] = new MorphologyProcessor();
            _processorRegistry["Contour"] = new ContourProcessor();
            _processorRegistry["ShockFilter"] = new ShockFilterProcessor();
            _processorRegistry["BandPassFilter"] = new BandPassFilterProcessor();

            // 16-bit processors (separate registry due to different base class)
            _processorRegistry16["GaussianBlur16"] = new GaussianBlurProcessor16();
            _processorRegistry16["FlatFieldCorrection16"] = new FlatFieldCorrectionProcessor16();

            _logger.Information("Registered {Count8} 8-bit and {Count16} 16-bit built-in image processors",
                _processorRegistry.Count, _processorRegistry16.Count);
        }

        public IReadOnlyList<string> GetAvailableProcessors()
        {
            var all = new List<string>(_processorRegistry.Keys);
            all.AddRange(_processorRegistry16.Keys);
            return all.AsReadOnly();
        }

        public void RegisterProcessor(string name, ImageProcessorBase processor)
        {
            if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Processor name cannot be empty", nameof(name));
            if (processor == null) throw new ArgumentNullException(nameof(processor));
            _processorRegistry[name] = processor;
            _logger.Information("Registered processor: {ProcessorName}", name);
        }

        public IReadOnlyList<ProcessorParameter> GetProcessorParameters(string processorName)
        {
            if (_processorRegistry.TryGetValue(processorName, out var processor))
                return processor.GetParameters().AsReadOnly();
            if (_processorRegistry16.TryGetValue(processorName, out var processor16))
                return processor16.GetParameters().AsReadOnly();
            throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
        }

        public ImageProcessorBase GetProcessor(string processorName)
        {
            if (_processorRegistry.TryGetValue(processorName, out var processor))
                return processor;
            throw new ArgumentException($"Processor not registered or is 16-bit only: {processorName}", nameof(processorName));
        }

        public string GetProcessorDisplayName(string processorName)
        {
            if (_processorRegistry.TryGetValue(processorName, out var p))
                return string.IsNullOrWhiteSpace(p.Name) ? processorName : p.Name;
            if (_processorRegistry16.TryGetValue(processorName, out var p16))
                return string.IsNullOrWhiteSpace(p16.Name) ? processorName : p16.Name;
            return processorName;
        }

        public async Task<BitmapSource> ProcessImageAsync(
            BitmapSource source,
            string processorName,
            IDictionary<string, object> parameters,
            IProgress<double> progress = null,
            CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (!_processorRegistry.TryGetValue(processorName, out var processor))
                throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));

            // Extract pixels on the UI thread (BitmapSource / FormatConvertedBitmap are DependencyObjects)
            var rawPixels = ImageConverter.ExtractGray8Pixels(source, out int imgWidth, out int imgHeight);

            return await Task.Run(() =>
            {
                cancellationToken.ThrowIfCancellationRequested();

                try
                {
                    var emguImage = ImageConverter.ToEmguCVFromPixels(rawPixels, imgWidth, imgHeight);

                    if (parameters != null)
                    {
                        foreach (var kvp in parameters)
                            processor.SetParameter(kvp.Key, kvp.Value);
                    }

                    progress?.Report(0.1);
                    var processedEmgu = processor.Process(emguImage);
                    progress?.Report(0.9);

                    var result = ImageConverter.ToBitmapSource(processedEmgu);
                    result.Freeze(); // must freeze before crossing thread boundary
                    progress?.Report(1.0);

                    return result;
                }
                catch (OperationCanceledException)
                {
                    throw;
                }
                catch (ArgumentException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    _logger.Error(ex, "Image processing failed for processor: {ProcessorName}", processorName);
                    throw new ImageProcessingException($"Image processing failed: {ex.Message}", ex);
                }
            }, cancellationToken);
        }

        public async Task<ushort[]> ProcessRawFrameAsync(
            ushort[] pixelData,
            int width,
            int height,
            string processorName,
            IDictionary<string, object> parameters,
            CancellationToken cancellationToken = default)
        {
            if (pixelData == null)
                throw new ArgumentException("pixelData cannot be null", nameof(pixelData));
            if (pixelData.Length != width * height)
                throw new ArgumentException(
                    $"pixelData length {pixelData.Length} does not match width*height {width * height}");

            if (!_processorRegistry16.TryGetValue(processorName, out var processor))
                throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));

            return await Task.Run(() =>
            {
                cancellationToken.ThrowIfCancellationRequested();

                var image = new Image<Gray, ushort>(width, height);
                for (int y = 0; y < height; y++)
                    for (int x = 0; x < width; x++)
                        image.Data[y, x, 0] = pixelData[y * width + x];

                if (parameters != null)
                {
                    foreach (var kvp in parameters)
                        processor.SetParameter(kvp.Key, kvp.Value);
                }

                var processed = processor.Process(image);

                var result = new ushort[width * height];
                for (int y = 0; y < height; y++)
                    for (int x = 0; x < width; x++)
                        result[y * width + x] = processed.Data[y, x, 0];

                return result;
            }, cancellationToken);
        }

        public void Dispose()
        {
            foreach (var processor in _processorRegistry.Values)
            {
                if (processor is IDisposable disposable)
                    disposable.Dispose();
            }
            _processorRegistry.Clear();

            foreach (var processor in _processorRegistry16.Values)
            {
                if (processor is IDisposable disposable)
                    disposable.Dispose();
            }
            _processorRegistry16.Clear();
        }
    }
}
