using BookReader.Models;
using Polly;
using Polly.Retry;
using SQLite;

namespace BookReader.Services;

public class DatabaseService : IDatabaseService
{
    private SQLiteAsyncConnection? _database;
    private readonly string _dbPath;
    private readonly ResiliencePipeline _retryPipeline;

    public DatabaseService()
    {
        _dbPath = Path.Combine(FileSystem.AppDataDirectory, Constants.Database.DatabaseFileName);

        _retryPipeline = new ResiliencePipelineBuilder()
            .AddRetry(new RetryStrategyOptions
            {
                ShouldHandle = new PredicateBuilder().Handle<SQLiteException>()
                    .Handle<Exception>(ex => ex.Message.Contains("database is locked") || ex.Message.Contains("busy")),
                MaxRetryAttempts = Constants.Database.RetryCount,
                DelayGenerator = context => new ValueTask<TimeSpan?>(
                    TimeSpan.FromMilliseconds(Constants.Database.RetryBaseDelayMs * Math.Pow(2, context.AttemptNumber))),
                OnRetry = context =>
                {
                    System.Diagnostics.Debug.WriteLine($"[Database] Retry {context.AttemptNumber + 1} after {context.RetryDelay.TotalMilliseconds}ms: {context.Outcome.Exception?.Message}");
                    return default;
                }
            })
            .Build();
    }

    public async Task InitializeAsync(CancellationToken ct = default)
    {
        if (_database != null)
        {
            return;
        }

        _database = new SQLiteAsyncConnection(_dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.SharedCache);

        await _retryPipeline.ExecuteAsync(async _ =>
        {
            await _database!.CreateTableAsync<Book>();
            await _database!.CreateTableAsync<AppSettings>();
            await _database!.CreateTableAsync<ReadingProgress>();
        }, ct);
    }

    private async Task EnsureInitializedAsync()
    {
        if (_database == null)
        {
            await InitializeAsync();
        }
    }

    public async Task<List<Book>> GetAllBooksAsync(CancellationToken ct = default)
    {
        await EnsureInitializedAsync();
        return await _retryPipeline.ExecuteAsync(async _ =>
            await _database!.Table<Book>().OrderByDescending(book => book.LastRead).ToListAsync(), ct);
    }

    public async Task<Book?> GetBookByIdAsync(int id, CancellationToken ct = default)
    {
        await EnsureInitializedAsync();
        return await _retryPipeline.ExecuteAsync(async _ =>
            await _database!.Table<Book>().Where(book => book.Id == id).FirstOrDefaultAsync(), ct);
    }

    public async Task<int> SaveBookAsync(Book book, CancellationToken ct = default)
    {
        await EnsureInitializedAsync();
        return await _retryPipeline.ExecuteAsync(async _ =>
        {
            if (book.Id != 0)
            {
                return await _database!.UpdateAsync(book);
            }

            return await _database!.InsertAsync(book);
        }, ct);
    }

    public async Task<int> UpdateBookAsync(Book book, CancellationToken ct = default)
    {
        await EnsureInitializedAsync();
        return await _retryPipeline.ExecuteAsync(async _ => await _database!.UpdateAsync(book), ct);
    }

    public async Task<int> DeleteBookAsync(Book book, CancellationToken ct = default)
    {
        await EnsureInitializedAsync();

        if (File.Exists(book.FilePath))
        {
            try
            {
                File.Delete(book.FilePath);
            }
            catch
            {
            }
        }

        await _retryPipeline.ExecuteAsync(async _ =>
            await _database!.Table<ReadingProgress>().DeleteAsync(progress => progress.BookId == book.Id), ct);

        return await _retryPipeline.ExecuteAsync(async _ => await _database!.DeleteAsync(book), ct);
    }

    public async Task<string?> GetSettingAsync(string key, CancellationToken ct = default)
    {
        await EnsureInitializedAsync();
        return await _retryPipeline.ExecuteAsync(async _ =>
        {
            var setting = await _database!.Table<AppSettings>().Where(item => item.Key == key).FirstOrDefaultAsync();
            return setting?.Value;
        }, ct);
    }

    public async Task SetSettingAsync(string key, string value, CancellationToken ct = default)
    {
        await EnsureInitializedAsync();
        await _retryPipeline.ExecuteAsync(async _ =>
        {
            var existing = await _database!.Table<AppSettings>().Where(item => item.Key == key).FirstOrDefaultAsync();
            if (existing != null)
            {
                existing.Value = value;
                await _database.UpdateAsync(existing);
            }
            else
            {
                await _database.InsertAsync(new AppSettings { Key = key, Value = value });
            }
        }, ct);
    }

    public async Task<Dictionary<string, string>> GetAllSettingsAsync(CancellationToken ct = default)
    {
        await EnsureInitializedAsync();
        return await _retryPipeline.ExecuteAsync(async _ =>
        {
            var settings = await _database!.Table<AppSettings>().ToListAsync();
            return settings.ToDictionary(setting => setting.Key, setting => setting.Value);
        }, ct);
    }

    public async Task SaveProgressAsync(ReadingProgress progress, CancellationToken ct = default)
    {
        await EnsureInitializedAsync();
        await _retryPipeline.ExecuteAsync(async _ =>
        {
            progress.Timestamp = DateTime.UtcNow;
            await _database!.InsertAsync(progress);
            await _database.ExecuteAsync(
                @"DELETE FROM ReadingProgress
                  WHERE BookId = ?
                    AND Id NOT IN (
                        SELECT Id FROM ReadingProgress
                        WHERE BookId = ?
                        ORDER BY Timestamp DESC
                        LIMIT ?
                    )",
                progress.BookId,
                progress.BookId,
                Constants.Database.MaxReadingHistoryEntriesPerBook);
        }, ct);
    }

    public async Task<ReadingProgress?> GetLatestProgressAsync(int bookId, CancellationToken ct = default)
    {
        await EnsureInitializedAsync();
        return await _retryPipeline.ExecuteAsync(async _ =>
            await _database!.Table<ReadingProgress>()
                .Where(progress => progress.BookId == bookId)
                .OrderByDescending(progress => progress.Timestamp)
                .FirstOrDefaultAsync(), ct);
    }
}
