using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using FootwearTest.Models;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;

namespace FootwearTest.Services;

public sealed class TestRunRepository
{
    private readonly ILogger<TestRunRepository> _logger;
    private readonly string _databasePath;

    public TestRunRepository(ILogger<TestRunRepository> logger)
    {
        _logger = logger;
        var directory = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "FootwearTest");
        Directory.CreateDirectory(directory);
        _databasePath = Path.Combine(directory, "footwear-test.db");
        EnsureCreated();
        _logger.LogInformation("Test run repository ready. DatabasePath={DatabasePath}", _databasePath);
    }

    public async Task<long> SaveAsync(TestRunRecord record)
    {
        try
        {
            await using var connection = CreateConnection();
            await connection.OpenAsync();
            var command = connection.CreateCommand();
            command.CommandText = """
            INSERT INTO TestRuns(Method, SampleDescription, Conditions, ResultSummary, DataJson, IsValid, CreatedAt)
            VALUES($method, $sample, $conditions, $summary, $data, $valid, $createdAt);
            SELECT last_insert_rowid();
            """;
            command.Parameters.AddWithValue("$method", record.Method);
            command.Parameters.AddWithValue("$sample", record.SampleDescription);
            command.Parameters.AddWithValue("$conditions", record.Conditions);
            command.Parameters.AddWithValue("$summary", record.ResultSummary);
            command.Parameters.AddWithValue("$data", record.DataJson);
            command.Parameters.AddWithValue("$valid", record.IsValid ? 1 : 0);
            command.Parameters.AddWithValue("$createdAt", record.CreatedAt.ToString("O"));
            var id = (long)(await command.ExecuteScalarAsync() ?? 0L);
            _logger.LogInformation("Test run saved. Id={Id}, Method={Method}, IsValid={IsValid}", id, record.Method, record.IsValid);
            return id;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to save test run. Method={Method}, IsValid={IsValid}", record.Method, record.IsValid);
            throw;
        }
    }

    public async Task<IReadOnlyList<TestRunSummary>> GetRecentAsync(int take = 50)
    {
        var items = new List<TestRunSummary>();
        await using var connection = CreateConnection();
        await connection.OpenAsync();
        var command = connection.CreateCommand();
        command.CommandText = """
            SELECT Id, Method, SampleDescription, ResultSummary, IsValid, CreatedAt
            FROM TestRuns
            ORDER BY Id DESC
            LIMIT $take;
            """;
        command.Parameters.AddWithValue("$take", take);
        await using var reader = await command.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            items.Add(new TestRunSummary(
                reader.GetInt64(0),
                reader.GetString(1),
                reader.GetString(2),
                reader.GetString(3),
                reader.GetInt32(4) == 1,
                DateTime.Parse(reader.GetString(5))));
        }

        return items;
    }

    public async Task<TestRunRecord?> GetLatestAsync()
    {
        await using var connection = CreateConnection();
        await connection.OpenAsync();
        var command = connection.CreateCommand();
        command.CommandText = """
            SELECT Id, Method, SampleDescription, Conditions, ResultSummary, DataJson, IsValid, CreatedAt
            FROM TestRuns
            ORDER BY Id DESC
            LIMIT 1;
            """;
        await using var reader = await command.ExecuteReaderAsync();
        return await reader.ReadAsync() ? ReadRecord(reader) : null;
    }

    public async Task<TestRunRecord?> GetByIdAsync(long id)
    {
        await using var connection = CreateConnection();
        await connection.OpenAsync();
        var command = connection.CreateCommand();
        command.CommandText = """
            SELECT Id, Method, SampleDescription, Conditions, ResultSummary, DataJson, IsValid, CreatedAt
            FROM TestRuns
            WHERE Id = $id;
            """;
        command.Parameters.AddWithValue("$id", id);
        await using var reader = await command.ExecuteReaderAsync();
        return await reader.ReadAsync() ? ReadRecord(reader) : null;
    }

    private TestRunRecord ReadRecord(SqliteDataReader reader)
    {
        return new TestRunRecord(
            reader.GetInt64(0),
            reader.GetString(1),
            reader.GetString(2),
            reader.GetString(3),
            reader.GetString(4),
            reader.GetString(5),
            reader.GetInt32(6) == 1,
            DateTime.Parse(reader.GetString(7)));
    }

    private SqliteConnection CreateConnection() => new($"Data Source={_databasePath}");

    private void EnsureCreated()
    {
        try
        {
            using var connection = CreateConnection();
            connection.Open();
            using var command = connection.CreateCommand();
            command.CommandText = """
            CREATE TABLE IF NOT EXISTS TestRuns
            (
                Id INTEGER PRIMARY KEY AUTOINCREMENT,
                Method TEXT NOT NULL,
                SampleDescription TEXT NOT NULL,
                Conditions TEXT NOT NULL,
                ResultSummary TEXT NOT NULL,
                DataJson TEXT NOT NULL,
                IsValid INTEGER NOT NULL,
                CreatedAt TEXT NOT NULL
            );
            """;
            command.ExecuteNonQuery();
        }
        catch (Exception ex)
        {
            _logger.LogCritical(ex, "Failed to initialize test run database. DatabasePath={DatabasePath}", _databasePath);
            throw;
        }
    }
}
