using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using PhotoboothUploader.Models;
using PhotoboothUploader.Services;

namespace PhotoboothUploader;

public partial class MainWindow : Window
{
    private const string DefaultBaseUrl = "https://fotospiel.app";
    private PhotoboothConnectClient _client;
    private readonly SettingsStore _settingsStore = new();
    private readonly UploadService _uploadService = new();
    private PhotoboothSettings _settings;
    private FileSystemWatcher? _watcher;
    private readonly Dictionary<string, UploadItem> _uploadsByPath = new(StringComparer.OrdinalIgnoreCase);
    private readonly HashSet<string> _failedPaths = new(StringComparer.OrdinalIgnoreCase);

    public ObservableCollection<UploadItem> RecentUploads { get; } = new();

    public MainWindow()
    {
        InitializeComponent();
        _settings = _settingsStore.Load();
        _settings.BaseUrl ??= DefaultBaseUrl;
        _client = new PhotoboothConnectClient(_settings.BaseUrl);
        _settingsStore.Save(_settings);
        DataContext = this;
        ApplySettings();
    }

    private async void ConnectButton_Click(object? sender, RoutedEventArgs e)
    {
        var code = (CodeBox.Text ?? string.Empty).Trim();

        if (code.Length != 6 || code.Any(ch => ch is < '0' or > '9'))
        {
            StatusText.Text = "Bitte einen gültigen 6-stelligen Code eingeben.";
            return;
        }

        ConnectButton.IsEnabled = false;
        StatusText.Text = "Verbinde...";

        var response = await _client.RedeemAsync(code);

        if (response.Data is null)
        {
            StatusText.Text = response.Message ?? "Verbindung fehlgeschlagen.";
            ConnectButton.IsEnabled = true;
            return;
        }

        _settings.UploadUrl = ResolveUploadUrl(response.Data.UploadUrl);
        _settings.Username = response.Data.Username;
        _settings.Password = response.Data.Password;
        _settings.ResponseFormat = response.Data.ResponseFormat;
        _settingsStore.Save(_settings);

        StatusText.Text = "Verbunden. Upload bereit.";
        PickFolderButton.IsEnabled = true;
        StartUploadPipelineIfReady();
        ConnectButton.IsEnabled = true;
    }

    private async void PickFolderButton_Click(object? sender, RoutedEventArgs e)
    {
        var options = new FolderPickerOpenOptions
        {
            Title = "Upload-Ordner auswählen",
            AllowMultiple = false,
        };

        var folders = await StorageProvider.OpenFolderPickerAsync(options);
        var folder = folders.FirstOrDefault();
        var localPath = folder?.TryGetLocalPath();

        if (string.IsNullOrWhiteSpace(localPath))
        {
            return;
        }

        _settings.WatchFolder = localPath;
        _settingsStore.Save(_settings);

        FolderText.Text = localPath;
        StartUploadPipelineIfReady();
    }

    private void ApplySettings()
    {
        if (!string.IsNullOrWhiteSpace(_settings.WatchFolder))
        {
            FolderText.Text = _settings.WatchFolder;
        }

        if (!string.IsNullOrWhiteSpace(_settings.UploadUrl))
        {
            StatusText.Text = "Verbunden. Upload bereit.";
            PickFolderButton.IsEnabled = true;
            StartUploadPipelineIfReady();
        }

        UpdateSteps();
    }

    private void StartUploadPipelineIfReady()
    {
        if (string.IsNullOrWhiteSpace(_settings.UploadUrl) || string.IsNullOrWhiteSpace(_settings.WatchFolder))
        {
            UpdateSteps();
            return;
        }

        _uploadService.Start(_settings, OnQueued, OnUploading, OnSuccess, OnFailure);
        StartWatcher(_settings.WatchFolder);
        UpdateSteps();
    }

    private void StartWatcher(string folder)
    {
        _watcher?.Dispose();

        _watcher = new FileSystemWatcher(folder)
        {
            IncludeSubdirectories = false,
            EnableRaisingEvents = true,
        };

        _watcher.Created += OnFileChanged;
        _watcher.Changed += OnFileChanged;
        _watcher.Renamed += OnFileRenamed;
    }

    private void OnFileChanged(object sender, FileSystemEventArgs e)
    {
        if (!IsSupportedImage(e.FullPath))
        {
            return;
        }

        _uploadService.Enqueue(e.FullPath, OnQueued);
    }

    private void OnFileRenamed(object sender, RenamedEventArgs e)
    {
        if (!IsSupportedImage(e.FullPath))
        {
            return;
        }

        _uploadService.Enqueue(e.FullPath, OnQueued);
    }

    private bool IsSupportedImage(string path)
    {
        var extension = Path.GetExtension(path)?.ToLowerInvariant();

        return extension is ".jpg" or ".jpeg" or ".png" or ".webp";
    }

    private void UpdateStatus(string message)
    {
        Dispatcher.UIThread.Post(() => StatusText.Text = message);
    }

    private void OnQueued(string path)
    {
        UpdateUpload(path, UploadStatus.Queued);
        UpdateStatusIfAllowed($"Wartet: {Path.GetFileName(path)}", false);
    }

    private void OnUploading(string path)
    {
        UpdateUpload(path, UploadStatus.Uploading);
        UpdateStatusIfAllowed($"Upload läuft: {Path.GetFileName(path)}", false);
    }

    private void OnSuccess(string path)
    {
        _failedPaths.Remove(path);
        UpdateUpload(path, UploadStatus.Success);
        UpdateStatusIfAllowed($"Hochgeladen: {Path.GetFileName(path)}", false);
    }

    private void OnFailure(string path)
    {
        _failedPaths.Add(path);
        UpdateUpload(path, UploadStatus.Failed);
        UpdateStatusIfAllowed($"Upload fehlgeschlagen: {Path.GetFileName(path)}", true);
        UpdateRetryButton();
    }

    private void UpdateUpload(string path, UploadStatus status)
    {
        Dispatcher.UIThread.Post(() =>
        {
            if (!_uploadsByPath.TryGetValue(path, out var item))
            {
                item = new UploadItem(path);
                _uploadsByPath[path] = item;
                RecentUploads.Insert(0, item);
            }

            item.Status = status;
            LastUploadText.Text = status == UploadStatus.Success
                ? $"Letzter Upload: {item.UpdatedLabel}"
                : LastUploadText.Text;

            while (RecentUploads.Count > 3)
            {
                var last = RecentUploads[^1];
                _uploadsByPath.Remove(last.Path);
                RecentUploads.RemoveAt(RecentUploads.Count - 1);
            }

            UpdateRetryButton();
        });
    }

    private void UpdateStatusIfAllowed(string message, bool important)
    {
        var quiet = QuietToggle?.IsChecked ?? false;

        if (quiet && !important)
        {
            return;
        }

        UpdateStatus(message);
    }

    private void UpdateRetryButton()
    {
        RetryFailedButton.IsEnabled = _failedPaths.Count > 0;
    }

    private void RetryFailedButton_Click(object? sender, RoutedEventArgs e)
    {
        foreach (var path in _failedPaths.ToList())
        {
            _uploadService.Enqueue(path, OnQueued);
        }

        _failedPaths.Clear();
        UpdateRetryButton();
    }

    private void UpdateSteps()
    {
        var hasCode = !string.IsNullOrWhiteSpace(_settings.UploadUrl);
        var hasFolder = !string.IsNullOrWhiteSpace(_settings.WatchFolder);
        var ready = hasCode && hasFolder;

        StepCodeText.Text = hasCode ? "1. Code eingeben ✓" : "1. Code eingeben";
        StepFolderText.Text = hasFolder ? "2. Upload-Ordner wählen ✓" : "2. Upload-Ordner wählen";
        StepReadyText.Text = ready ? "3. Upload läuft ✓" : "3. Upload läuft";
    }

    private string? ResolveUploadUrl(string? uploadUrl)
    {
        if (string.IsNullOrWhiteSpace(uploadUrl))
        {
            return uploadUrl;
        }

        if (Uri.TryCreate(uploadUrl, UriKind.Absolute, out _))
        {
            return uploadUrl;
        }

        var baseUri = new Uri(_settings.BaseUrl ?? DefaultBaseUrl, UriKind.Absolute);
        return new Uri(baseUri, uploadUrl).ToString();
    }
}
