using BookReader.Models;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
using System.Text;

namespace BookReader.Services;

public class CalibreWebService : ICalibreWebService
{
    private readonly HttpClient _httpClient;
    private readonly ICoverCacheService _coverCacheService;
    private string _baseUrl = string.Empty;

    public CalibreWebService(HttpClient httpClient, ICoverCacheService coverCacheService)
    {
        _httpClient = httpClient;
        _coverCacheService = coverCacheService;
        _httpClient.Timeout = TimeSpan.FromSeconds(Constants.Network.HttpClientTimeoutSeconds);
    }

    public void Configure(string url, string username, string password)
    {
        _baseUrl = url.Trim().TrimEnd('/');

        if (!string.IsNullOrWhiteSpace(username))
        {
            var authBytes = Encoding.ASCII.GetBytes($"{username}:{password}");
            _httpClient.DefaultRequestHeaders.Authorization =
                new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authBytes));
        }
        else
        {
            _httpClient.DefaultRequestHeaders.Authorization = null;
        }
    }

    public async Task<bool> TestConnectionAsync(string url, string username, string password)
    {
        try
        {
            Configure(url, username, password);
            var response = await _httpClient.GetAsync($"{_baseUrl}/ajax/search?query=&num=1");
            return response.IsSuccessStatusCode;
        }
        catch
        {
            return false;
        }
    }

    public async Task<Result<List<CalibreBook>>> GetBooksAsync(string? searchQuery = null, int page = 0, int pageSize = Constants.Network.CalibrePageSize)
    {
        if (string.IsNullOrWhiteSpace(_baseUrl))
        {
            return Result<List<CalibreBook>>.Failure("Сначала укажите адрес сервера Calibre в настройках.");
        }

        var books = new List<CalibreBook>();

        try
        {
            var offset = page * pageSize;
            var query = string.IsNullOrWhiteSpace(searchQuery) ? string.Empty : Uri.EscapeDataString(searchQuery.Trim());
            var url = $"{_baseUrl}/ajax/search?query={query}&num={pageSize}&offset={offset}&sort=timestamp&sort_order=desc";

            var response = await _httpClient.GetStringAsync(url);
            var json = JObject.Parse(response);
            var bookIds = json["book_ids"]?.ToObject<List<int>>() ?? new List<int>();

            var semaphore = new SemaphoreSlim(4);
            var tasks = bookIds.Select(async bookId =>
            {
                await semaphore.WaitAsync();
                try
                {
                    return await LoadBookDataAsync(bookId);
                }
                finally
                {
                    semaphore.Release();
                }
            });

            var results = await Task.WhenAll(tasks);
            books.AddRange(results.Where(book => book != null)!);

            return Result<List<CalibreBook>>.Success(books);
        }
        catch (TaskCanceledException ex)
        {
            return Result<List<CalibreBook>>.Failure(new TimeoutException("Сервер Calibre отвечает слишком долго.", ex));
        }
        catch (HttpRequestException ex)
        {
            return Result<List<CalibreBook>>.Failure(new HttpRequestException("Не удалось подключиться к серверу Calibre.", ex));
        }
        catch (Exception ex)
        {
            return Result<List<CalibreBook>>.Failure(new Exception("Не удалось загрузить каталог Calibre.", ex));
        }
    }

    private async Task<CalibreBook?> LoadBookDataAsync(int bookId)
    {
        try
        {
            var bookUrl = $"{_baseUrl}/ajax/book/{bookId}";
            var bookResponse = await _httpClient.GetStringAsync(bookUrl);
            var bookJson = JObject.Parse(bookResponse);

            var formats = bookJson["formats"]?.ToObject<List<string>>() ?? new List<string>();
            var supportedFormat = formats.FirstOrDefault(format =>
                format.Equals("EPUB", StringComparison.OrdinalIgnoreCase) ||
                format.Equals("FB2", StringComparison.OrdinalIgnoreCase));

            if (supportedFormat == null)
            {
                return null;
            }

            var authors = bookJson["authors"]?.ToObject<List<string>>() ?? new List<string>();

            var calibreBook = new CalibreBook
            {
                Id = bookId.ToString(),
                Title = bookJson["title"]?.ToString() ?? "Без названия",
                Author = string.Join(", ", authors),
                Format = supportedFormat.ToLowerInvariant(),
                CoverUrl = $"{_baseUrl}/get/cover/{bookId}",
                DownloadUrl = $"{_baseUrl}/get/{supportedFormat}/{bookId}"
            };

            var cacheKey = $"cover_{bookId}";
            calibreBook.CoverImage = await _coverCacheService.GetCoverAsync(cacheKey);

            if (calibreBook.CoverImage == null)
            {
                try
                {
                    calibreBook.CoverImage = await _httpClient.GetByteArrayAsync(calibreBook.CoverUrl);
                    await _coverCacheService.SetCoverAsync(cacheKey, calibreBook.CoverImage);
                }
                catch
                {
                }
            }

            return calibreBook;
        }
        catch
        {
            return null;
        }
    }

    public async Task<string> DownloadBookAsync(CalibreBook book, IProgress<double>? progress = null)
    {
        var booksDir = Path.Combine(FileSystem.AppDataDirectory, Constants.Files.BooksFolder);
        Directory.CreateDirectory(booksDir);

        var fileName = $"{Guid.NewGuid()}.{book.Format}";
        var filePath = Path.Combine(booksDir, fileName);

        using var response = await _httpClient.GetAsync(book.DownloadUrl, HttpCompletionOption.ResponseHeadersRead);
        response.EnsureSuccessStatusCode();

        var totalBytes = response.Content.Headers.ContentLength ?? -1;
        var bytesRead = 0L;

        using var contentStream = await response.Content.ReadAsStreamAsync();
        using var fileStream = File.Create(filePath);

        var buffer = new byte[Constants.Network.DownloadBufferSize];
        int read;

        while ((read = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            await fileStream.WriteAsync(buffer, 0, read);
            bytesRead += read;

            if (totalBytes > 0)
            {
                progress?.Report((double)bytesRead / totalBytes);
            }
        }

        return filePath;
    }
}
