using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Face;
using Emgu.CV.Structure;
using Emgu.CV.Util;
using FaceRecognitionApp.Models;
using System.Drawing;
using System.Runtime.InteropServices;

namespace FaceRecognitionApp.Services;

public class FaceRecognitionService : IDisposable
{
    private readonly LBPHFaceRecognizer _recognizer;
    private readonly CascadeClassifier _cascade;
    private readonly DatabaseService _db;
    private List<Employee> _employees = new();
    private bool _isTrained = false;

    // Коэффициент уменьшения кадра для ускорения поиска
    private const double ResizeScale = 0.5;
    private const int OriginalMinSize = 30;
    private readonly Size _scaledMinSize;

    public FaceRecognitionService(DatabaseService db)
    {
        _db = db;
        var cascadePath = Path.Combine(AppContext.BaseDirectory, "haarcascade_frontalface_default.xml");
        if (!File.Exists(cascadePath))
            throw new FileNotFoundException($"Cascade file missing at: {cascadePath}");

        _cascade = new CascadeClassifier(cascadePath);
        _recognizer = new LBPHFaceRecognizer(1, 8, 8, 8, 80.0);

        // Вычисляем минимальный размер лица для уменьшенного кадра
        int scaledMin = (int)(OriginalMinSize * ResizeScale);
        _scaledMinSize = new Size(Math.Max(scaledMin, 10), Math.Max(scaledMin, 10));

        TrainModel();
    }

    private void TrainModel()
    {
        _employees = _db.GetAllEmployees();
        if (!_employees.Any()) return;

        using var images = new VectorOfMat();
        using var labels = new VectorOfInt();

        foreach (var emp in _employees)
        {
            if (emp.Photo == null || emp.Photo.Length == 0) continue;

            try
            {
                using var ms = new MemoryStream(emp.Photo);
                using var bmp = new Bitmap(ms);
                using var mat = BitmapToMat(bmp);
                using var gray = new Mat();

                CvInvoke.CvtColor(mat, gray, ColorConversion.Bgr2Gray);

                // Ищем лицо на фото сотрудника для обрезки
                var rects = _cascade.DetectMultiScale(gray, 1.1, 5, new Size(30, 30));

                if (rects.Length > 0)
                {
                    using var faceMat = new Mat(gray, rects[0]);
                    using var resized = new Mat();
                    CvInvoke.Resize(faceMat, resized, new Size(100, 100));

                    images.Push(resized);
                    labels.Push(new int[] { emp.Id });
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Training error for {emp.Name}: {ex.Message}");
            }
        }

        if (images.Size > 0)
        {
            _recognizer.Train(images, labels);
            _isTrained = true;
            Console.WriteLine($"✅ Trained on {images.Size} faces.");
        }
        else
        {
            Console.WriteLine("⚠️ No valid faces found for training.");
        }
    }

    public DetectedFace? ProcessFrame(Mat frame)
    {
        if (frame == null || frame.IsEmpty) return null;

        // === УСКОРЕНИЕ: Уменьшаем кадр для поиска лиц ===
        using var smallFrame = new Mat();
        CvInvoke.Resize(frame, smallFrame, new Size(), ResizeScale, ResizeScale, Inter.Linear);

        using var gray = new Mat();
        CvInvoke.CvtColor(smallFrame, gray, ColorConversion.Bgr2Gray);

        // Ищем лица на уменьшенном кадре
        var rects = _cascade.DetectMultiScale(gray, 1.1, 5, _scaledMinSize);

        if (rects.Length == 0)
            return null;

        var smallRect = rects[0];

        // === ВОССТАНОВЛЕНИЕ координат к оригинальному размеру ===
        int invScale = (int)(1.0 / ResizeScale);
        var originalRect = new Rectangle(
            smallRect.X * invScale,
            smallRect.Y * invScale,
            smallRect.Width * invScale,
            smallRect.Height * invScale
        );

        // Проверка границ по оригинальному кадру
        if (originalRect.X < 0 || originalRect.Y < 0 ||
            originalRect.Right > frame.Width || originalRect.Bottom > frame.Height)
            return null;

        // Вырезаем лицо из ОРИГИНАЛЬНОГО кадра (лучшее качество для распознавания)
        using var faceMat = new Mat(frame, originalRect);
        using var faceGray = new Mat();
        CvInvoke.CvtColor(faceMat, faceGray, ColorConversion.Bgr2Gray);

        using var resized = new Mat();
        CvInvoke.Resize(faceGray, resized, new Size(100, 100));

        var result = new DetectedFace();

        if (_isTrained)
        {
            try
            {
                var prediction = _recognizer.Predict(resized);

                int label = prediction.Label;
                double confidence = prediction.Distance;

                if (label != -1 && confidence < 80)
                {
                    result.IsRecognized = true;
                    result.MatchedEmployee = _employees.FirstOrDefault(e => e.Id == label);
                    result.Confidence = confidence;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Prediction error: {ex.Message}");
            }
        }

        // Конвертация в JPEG для веб-интерфейса
        using var ms = new MemoryStream();
        using var bmp = MatToBitmap(resized);
        bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
        result.ImageData = ms.ToArray();

        return result;
    }

    /// <summary>
    /// Безопасная конвертация Bitmap → Mat через промежуточный буфер
    /// </summary>
    private static Mat BitmapToMat(Bitmap bitmap)
    {
        var mat = new Mat(bitmap.Height, bitmap.Width, DepthType.Cv8U, 3);

        var data = bitmap.LockBits(
            new Rectangle(0, 0, bitmap.Width, bitmap.Height),
            System.Drawing.Imaging.ImageLockMode.ReadOnly,
            System.Drawing.Imaging.PixelFormat.Format24bppRgb);

        try
        {
            int length = Math.Abs(data.Stride) * bitmap.Height;
            byte[] buffer = new byte[length];
            Marshal.Copy(data.Scan0, buffer, 0, length);
            Marshal.Copy(buffer, 0, mat.DataPointer, length);
        }
        finally
        {
            bitmap.UnlockBits(data);
        }

        return mat;
    }

    /// <summary>
    /// Безопасная конвертация Mat → Bitmap через промежуточный буфер
    /// </summary>
    private static Bitmap MatToBitmap(Mat mat)
    {
        var colorMat = new Mat();
        if (mat.NumberOfChannels == 1)
            CvInvoke.CvtColor(mat, colorMat, ColorConversion.Gray2Bgr);
        else
            mat.CopyTo(colorMat);

        var bmp = new Bitmap(colorMat.Width, colorMat.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

        var data = bmp.LockBits(
            new Rectangle(0, 0, bmp.Width, bmp.Height),
            System.Drawing.Imaging.ImageLockMode.WriteOnly,
            System.Drawing.Imaging.PixelFormat.Format24bppRgb);

        try
        {
            int length = Math.Abs(data.Stride) * colorMat.Height;
            byte[] buffer = new byte[length];
            Marshal.Copy(colorMat.DataPointer, buffer, 0, length);
            Marshal.Copy(buffer, 0, data.Scan0, length);
        }
        finally
        {
            bmp.UnlockBits(data);
            colorMat.Dispose();
        }

        return bmp;
    }

    public void Dispose()
    {
        _cascade?.Dispose();
        _recognizer?.Dispose();
    }
}