using System.IO;
using Timer = System.Timers.Timer;
using System.Threading.Tasks;
using System.Windows;
using CamBooth.App.Core.AppSettings;
using CamBooth.App.Core.Logging;
using CamBooth.App.Features.PhotoPrismUpload;
using CamBooth.App.Features.PictureGallery;
using EOSDigital.API;
using EOSDigital.SDK;

namespace CamBooth.App.Features.Camera;

public class CameraService : IDisposable
{
    private readonly AppSettingsService _appSettings;
    private readonly Logger _logger;
    private readonly PictureGalleryService _pictureGalleryService;
    private readonly PhotoPrismUploadQueueService _photoPrismUploadQueueService;
    private readonly ICanonAPI _canonApi;

    private ICamera? _mainCamera;
    private List<ICamera>? _camList;
    private bool _isConnected;
    private Timer? _shutdownKeepAliveTimer;

    /// <summary>Fires whenever the camera delivers a new live-view frame.</summary>
    public event Action<Stream>? LiveViewUpdated;

    public bool IsConnected => _isConnected && _mainCamera?.SessionOpen == true;


    public CameraService(
        Logger logger,
        AppSettingsService appSettings,
        PictureGalleryService pictureGalleryService,
        PhotoPrismUploadQueueService photoPrismUploadQueueService,
        ICanonAPI canonApi)
    {
        _logger = logger;
        _appSettings = appSettings;
        _pictureGalleryService = pictureGalleryService;
        _photoPrismUploadQueueService = photoPrismUploadQueueService;
        _canonApi = canonApi;
    }


    public void Dispose()
    {
        CloseSession();
        _canonApi.Dispose();
        _mainCamera?.Dispose();
    }


    public void ConnectCamera()
    {
        ErrorHandler.SevereErrorHappened += ErrorHandler_SevereErrorHappened;
        ErrorHandler.NonSevereErrorHappened += ErrorHandler_NonSevereErrorHappened;

        try
        {
            RefreshCameraList();

            const int maxRetries = 3;
            const int retryDelayMs = 750;

            for (int attempt = 0; attempt < maxRetries; attempt++)
            {
                if (_camList?.Any() == true) break;

                if (attempt < maxRetries - 1)
                {
                    Thread.Sleep(retryDelayMs);
                    RefreshCameraList();
                }
            }

            if (_camList?.Any() != true)
                throw new InvalidOperationException("No cameras found after multiple attempts.");

            _mainCamera = _camList[0];
            _logger.Info($"Camera found: {_mainCamera.DeviceName}");

            OpenSession();
            SetSaveToComputer();
            StartLiveView();
            _isConnected = true;
        }
        catch (Exception ex)
        {
            _logger.Error($"Error connecting camera: {ex.Message}");
            throw;
        }
    }


    public void CloseSession()
    {
        try
        {
            if (_mainCamera?.SessionOpen == true)
            {
                StopShutdownKeepAlive();
                _mainCamera.CloseSession();
                _logger.Info("Camera session closed");
            }
        }
        catch (Exception ex)
        {
            _logger.Error($"Error closing camera session: {ex.Message}");
        }

        _isConnected = false;
    }


    public void TakePhoto()
    {
        if (_mainCamera == null) throw new InvalidOperationException("Camera not connected.");
        _mainCamera.TakePhoto();
    }


    public async Task PrepareFocusAsync(int focusTimeoutMs = 1500)
    {
        if (_mainCamera is not EOSDigital.API.Camera sdkCamera)
        {
            await Task.Delay(200);
            return;
        }

        var focusCompleted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

        void FocusStateChanged(EOSDigital.API.Camera sender, StateEventID eventId, int parameter)
        {
            if (eventId == StateEventID.AfResult)
                focusCompleted.TrySetResult(true);
        }

        sdkCamera.StateChanged += FocusStateChanged;

        try
        {
            await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Halfway));
            await Task.WhenAny(focusCompleted.Task, Task.Delay(focusTimeoutMs));
        }
        catch (Exception ex)
        {
            _logger.Error(ex.Message);
        }
        finally
        {
            try
            {
                await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF));
            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message);
            }

            sdkCamera.StateChanged -= FocusStateChanged;
        }
    }


    private void RefreshCameraList() => _camList = _canonApi.GetCameraList();


    private void OpenSession()
    {
        if (_mainCamera == null)
            throw new InvalidOperationException("Camera reference is null.");

        if (_mainCamera.SessionOpen)
        {
            _logger.Info($"Session already open for {_mainCamera.DeviceName}");
            return;
        }

        _logger.Info($"Opening session for: {_mainCamera.DeviceName}");

        const int maxRetries = 3;
        const int retryDelayMs = 1000;

        for (int attempt = 0; attempt < maxRetries; attempt++)
        {
            try
            {
                _mainCamera.OpenSession();
                break;
            }
            catch (Exception ex) when (attempt < maxRetries - 1 && IsSessionNotOpenError(ex))
            {
                _logger.Warning($"OpenSession attempt {attempt + 1}/{maxRetries} failed, retrying...");
                System.Threading.Thread.Sleep(retryDelayMs);
                RefreshCameraList();
                if (_camList?.Any() == true)
                    _mainCamera = _camList[0];
            }
            catch (Exception ex)
            {
                _logger.Error($"Failed to open session: {ex.Message}");
                throw;
            }
        }

        _logger.Info("Session opened successfully");
        _mainCamera.StateChanged += MainCamera_StateChanged;
        _mainCamera.DownloadReady += MainCamera_DownloadReady;
        _mainCamera.LiveViewUpdated += MainCamera_LiveViewUpdated;
        _mainCamera.LiveViewStopped += MainCamera_LiveViewStopped;
        _mainCamera.CameraHasShutdown += MainCamera_CameraHasShutdown;
        StartShutdownKeepAlive();
    }


    private void SetSaveToComputer()
    {
        _mainCamera!.SetSetting(PropertyID.SaveTo, (int)SaveTo.Host);
        _mainCamera.SetCapacity(4096, int.MaxValue);
    }


    private void StartLiveView()
    {
        try
        {
            if (!_mainCamera!.IsLiveViewOn)
                _mainCamera.StartLiveView();
        }
        catch (Exception ex)
        {
            _logger.Error(ex.Message);
        }
    }


    private static bool IsSessionNotOpenError(Exception ex)
    {
        const string errorName = "SESSION_NOT_OPEN";
        return ex.Message.Contains(errorName) || (ex.InnerException?.Message?.Contains(errorName) ?? false);
    }


    #region Camera event handlers

    private void MainCamera_LiveViewUpdated(ICamera sender, Stream img) =>
        LiveViewUpdated?.Invoke(img);

    private void MainCamera_LiveViewStopped(ICamera sender)
    {
        if (!_isConnected) return;

        _logger.Warning("LiveView stopped; attempting restart.");
        try
        {
            sender.StartLiveView();
        }
        catch (Exception ex)
        {
            _logger.Error($"Failed to restart LiveView: {ex.Message}");
        }
    }


    private void MainCamera_StateChanged(EOSDigital.API.Camera sender, StateEventID eventID, int parameter)
    {
        try
        {
            if (eventID == StateEventID.WillSoonShutDown || eventID == StateEventID.ShutDownTimerUpdate)
                ExtendShutdownTimer();

            if (eventID == StateEventID.Shutdown && _isConnected)
                Application.Current.Dispatcher.Invoke(CloseSession);
        }
        catch (Exception ex)
        {
            _logger.Error(ex.Message);
        }
    }


    private void MainCamera_DownloadReady(ICamera sender, IDownloadInfo info)
    {
        _logger.Debug("Download ready");
        try
        {
            info.FileName = $"img_{Guid.NewGuid()}.jpg";
            sender.DownloadFile(info, _appSettings.PictureLocation);
            var savedPath = Path.Combine(_appSettings.PictureLocation!, info.FileName);
            _logger.Debug($"Download complete: {savedPath}");

            Application.Current.Dispatcher.Invoke(() =>
            {
                _pictureGalleryService.IncrementNewPhotoCount();
                _pictureGalleryService.LoadThumbnailsToCache();
            });

            _photoPrismUploadQueueService.QueueNewPhoto(savedPath);
        }
        catch (Exception ex)
        {
            _logger.Error(ex.Message);
        }
    }


    private void ErrorHandler_NonSevereErrorHappened(object sender, ErrorCode ex) =>
        _logger.Error($"SDK Error: {ex} (0x{(int)ex:X})");


    private void ErrorHandler_SevereErrorHappened(object sender, Exception ex) =>
        _logger.Error(ex.Message);

    private void MainCamera_CameraHasShutdown(ICamera sender)
    {
        try
        {
            StopShutdownKeepAlive();
        }
        catch (Exception ex)
        {
            _logger.Error($"Failed stopping shutdown keep-alive: {ex.Message}");
        }
    }

    #endregion

    private void StartShutdownKeepAlive()
    {
        if (_shutdownKeepAliveTimer != null) return;

        _shutdownKeepAliveTimer = new Timer(20_000);
        _shutdownKeepAliveTimer.AutoReset = true;
        _shutdownKeepAliveTimer.Elapsed += (_, _) => ExtendShutdownTimer();
        _shutdownKeepAliveTimer.Start();
    }

    private void StopShutdownKeepAlive()
    {
        if (_shutdownKeepAliveTimer == null) return;

        _shutdownKeepAliveTimer.Stop();
        _shutdownKeepAliveTimer.Dispose();
        _shutdownKeepAliveTimer = null;
    }

    private void ExtendShutdownTimer()
    {
        if (_mainCamera == null || !_mainCamera.SessionOpen) return;
        try
        {
            _mainCamera.SendCommand(CameraCommand.ExtendShutDownTimer);
        }
        catch (Exception ex)
        {
            _logger.Warning($"ExtendShutDownTimer failed: {ex.Message}");
        }
    }
}
