using Journal.Core.Models;
using Journal.Core.Services.Database;
using Microsoft.Data.Sqlite;

namespace Journal.Core.Repositories;

public sealed class SqliteListRepository(IDatabaseSessionService session) : IListRepository
{
    private readonly IDatabaseSessionService _session = session;

    public List<ListDocument> GetAll()
    {
        var conn = _session.GetConnection();
        return ReadAll(conn);
    }

    public ListDocument? GetById(Guid id)
    {
        var conn = _session.GetConnection();
        return ReadById(conn, id);
    }

    public void Add(ListDocument list)
    {
        ArgumentNullException.ThrowIfNull(list);
        var conn = _session.GetConnection();
        Insert(conn, list);
    }

    public bool Update(Guid id, string? label = null, string? content = null)
    {
        var conn = _session.GetConnection();
        var existing = ReadById(conn, id);
        if (existing is null)
            return false;

        if (label is not null)
        {
            if (string.IsNullOrWhiteSpace(label))
                throw new ArgumentException("Label cannot be empty", nameof(label));
            existing.Label = label.Trim();
        }

        if (content is not null)
            existing.Content = content;

        existing.UpdatedAt = DateTimeOffset.Now;
        UpdateRow(conn, existing);
        return true;
    }

    public bool Remove(Guid id)
    {
        var conn = _session.GetConnection();
        return Delete(conn, id);
    }

    // ── Private helpers ──────────────────────────────────────────────

    private static void Insert(SqliteConnection conn, ListDocument list)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            INSERT INTO lists (guid, label, content, created_at, updated_at)
            VALUES (@guid, @label, @content, @createdAt, @updatedAt);
            """;
        cmd.Parameters.AddWithValue("@guid", list.Id.ToString("D"));
        cmd.Parameters.AddWithValue("@label", list.Label);
        cmd.Parameters.AddWithValue("@content", list.Content);
        cmd.Parameters.AddWithValue("@createdAt", list.CreatedAt.ToString("O"));
        cmd.Parameters.AddWithValue("@updatedAt", list.UpdatedAt.ToString("O"));
        cmd.ExecuteNonQuery();
    }

    private static void UpdateRow(SqliteConnection conn, ListDocument list)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            UPDATE lists SET label = @label, content = @content, updated_at = @updatedAt
            WHERE guid = @guid;
            """;
        cmd.Parameters.AddWithValue("@guid", list.Id.ToString("D"));
        cmd.Parameters.AddWithValue("@label", list.Label);
        cmd.Parameters.AddWithValue("@content", list.Content);
        cmd.Parameters.AddWithValue("@updatedAt", list.UpdatedAt.ToString("O"));
        cmd.ExecuteNonQuery();
    }

    private static bool Delete(SqliteConnection conn, Guid id)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "DELETE FROM lists WHERE guid = @guid;";
        cmd.Parameters.AddWithValue("@guid", id.ToString("D"));
        return cmd.ExecuteNonQuery() > 0;
    }

    private static ListDocument? ReadById(SqliteConnection conn, Guid id)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "SELECT guid, label, content, created_at, updated_at FROM lists WHERE guid = @guid;";
        cmd.Parameters.AddWithValue("@guid", id.ToString("D"));

        using var reader = cmd.ExecuteReader();
        return reader.Read() ? MapRow(reader) : null;
    }

    private static List<ListDocument> ReadAll(SqliteConnection conn)
    {
        var results = new List<ListDocument>();
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "SELECT guid, label, content, created_at, updated_at FROM lists ORDER BY created_at;";

        using var reader = cmd.ExecuteReader();
        while (reader.Read())
            results.Add(MapRow(reader));

        return results;
    }

    private static ListDocument MapRow(SqliteDataReader reader)
    {
        var guid = Guid.Parse(reader.GetString(0));
        var label = reader.GetString(1);
        var content = reader.IsDBNull(2) ? "" : reader.GetString(2);
        var createdAt = reader.IsDBNull(3) ? DateTimeOffset.MinValue : DateTimeOffset.Parse(reader.GetString(3));
        var updatedAt = reader.IsDBNull(4) ? createdAt : DateTimeOffset.Parse(reader.GetString(4));
        return new ListDocument(guid, label, content, createdAt, updatedAt);
    }
}
