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

namespace Journal.Core.Repositories;

public sealed class SqliteTodoRepository(IDatabaseSessionService session) : ITodoRepository
{
    private readonly IDatabaseSessionService _session = session;

    // ── Lists ────────────────────────────────────────────────────────

    public List<TodoList> GetAllLists()
    {
        var conn = _session.GetConnection();
        return ReadAllLists(conn);
    }

    public TodoList? GetListById(Guid id)
    {
        var conn = _session.GetConnection();
        return ReadListById(conn, id);
    }

    public void AddList(TodoList list)
    {
        ArgumentNullException.ThrowIfNull(list);
        var conn = _session.GetConnection();
        InsertList(conn, list);
    }

    public bool UpdateList(Guid id, string? label = null)
    {
        var conn = _session.GetConnection();
        var existing = ReadListById(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();
        }

        UpdateListRow(conn, existing);
        return true;
    }

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

    // ── Items ────────────────────────────────────────────────────────

    public List<TodoItem> GetItemsByListId(Guid listId)
    {
        var conn = _session.GetConnection();
        return ReadItemsByListId(conn, listId);
    }

    public TodoItem? GetItemById(Guid id)
    {
        var conn = _session.GetConnection();
        return ReadItemById(conn, id);
    }

    public void AddItem(TodoItem item)
    {
        ArgumentNullException.ThrowIfNull(item);
        var conn = _session.GetConnection();
        InsertItem(conn, item);
    }

    public bool UpdateItem(Guid id, string? text = null, bool? done = null, int? sortOrder = null)
    {
        var conn = _session.GetConnection();
        var existing = ReadItemById(conn, id);
        if (existing is null)
            return false;

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

        if (done.HasValue)
            existing.Done = done.Value;

        if (sortOrder.HasValue)
            existing.SortOrder = sortOrder.Value;

        UpdateItemRow(conn, existing);
        return true;
    }

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

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

    private static void InsertList(SqliteConnection conn, TodoList list)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            INSERT INTO todo_lists (guid, label, created_at)
            VALUES (@guid, @label, @createdAt);
            """;
        cmd.Parameters.AddWithValue("@guid", list.Id.ToString("D"));
        cmd.Parameters.AddWithValue("@label", list.Label);
        cmd.Parameters.AddWithValue("@createdAt", list.CreatedAt.ToString("O"));
        cmd.ExecuteNonQuery();
    }

    private static void UpdateListRow(SqliteConnection conn, TodoList list)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "UPDATE todo_lists SET label = @label WHERE guid = @guid;";
        cmd.Parameters.AddWithValue("@guid", list.Id.ToString("D"));
        cmd.Parameters.AddWithValue("@label", list.Label);
        cmd.ExecuteNonQuery();
    }

    private static bool DeleteList(SqliteConnection conn, Guid id)
    {
        using var tx = conn.BeginTransaction();

        var rowId = GetListRowId(conn, id);
        if (rowId.HasValue)
        {
            using var delItems = conn.CreateCommand();
            delItems.CommandText = "DELETE FROM todo_items WHERE list_id = @listId;";
            delItems.Parameters.AddWithValue("@listId", rowId.Value);
            delItems.ExecuteNonQuery();
        }

        using var delList = conn.CreateCommand();
        delList.CommandText = "DELETE FROM todo_lists WHERE guid = @guid;";
        delList.Parameters.AddWithValue("@guid", id.ToString("D"));
        var rows = delList.ExecuteNonQuery();

        tx.Commit();
        return rows > 0;
    }

    private static TodoList? ReadListById(SqliteConnection conn, Guid id)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "SELECT guid, label, created_at FROM todo_lists WHERE guid = @guid;";
        cmd.Parameters.AddWithValue("@guid", id.ToString("D"));

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

    private static List<TodoList> ReadAllLists(SqliteConnection conn)
    {
        var results = new List<TodoList>();
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "SELECT guid, label, created_at FROM todo_lists ORDER BY created_at;";

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

        return results;
    }

    private static long? GetListRowId(SqliteConnection conn, Guid guid)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "SELECT id FROM todo_lists WHERE guid = @guid;";
        cmd.Parameters.AddWithValue("@guid", guid.ToString("D"));
        var result = cmd.ExecuteScalar();
        return result is long id ? id : null;
    }

    private static TodoList MapListRow(SqliteDataReader reader)
    {
        var guid = Guid.Parse(reader.GetString(0));
        var label = reader.GetString(1);
        var createdAt = reader.IsDBNull(2) ? DateTimeOffset.MinValue : DateTimeOffset.Parse(reader.GetString(2));
        return new TodoList(guid, label, createdAt);
    }

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

    private static void InsertItem(SqliteConnection conn, TodoItem item)
    {
        var listRowId = GetListRowId(conn, item.ListId)
            ?? throw new InvalidOperationException($"Todo list {item.ListId} not found");

        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            INSERT INTO todo_items (guid, list_id, text, done, sort_order)
            VALUES (@guid, @listId, @text, @done, @sortOrder);
            """;
        cmd.Parameters.AddWithValue("@guid", item.Id.ToString("D"));
        cmd.Parameters.AddWithValue("@listId", listRowId);
        cmd.Parameters.AddWithValue("@text", item.Text);
        cmd.Parameters.AddWithValue("@done", item.Done ? 1 : 0);
        cmd.Parameters.AddWithValue("@sortOrder", item.SortOrder);
        cmd.ExecuteNonQuery();
    }

    private static void UpdateItemRow(SqliteConnection conn, TodoItem item)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            UPDATE todo_items SET text = @text, done = @done, sort_order = @sortOrder
            WHERE guid = @guid;
            """;
        cmd.Parameters.AddWithValue("@guid", item.Id.ToString("D"));
        cmd.Parameters.AddWithValue("@text", item.Text);
        cmd.Parameters.AddWithValue("@done", item.Done ? 1 : 0);
        cmd.Parameters.AddWithValue("@sortOrder", item.SortOrder);
        cmd.ExecuteNonQuery();
    }

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

    private static TodoItem? ReadItemById(SqliteConnection conn, Guid id)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            SELECT ti.guid, tl.guid, ti.text, ti.done, ti.sort_order
            FROM todo_items ti
            INNER JOIN todo_lists tl ON tl.id = ti.list_id
            WHERE ti.guid = @guid;
            """;
        cmd.Parameters.AddWithValue("@guid", id.ToString("D"));

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

    private static List<TodoItem> ReadItemsByListId(SqliteConnection conn, Guid listId)
    {
        var results = new List<TodoItem>();
        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            SELECT ti.guid, tl.guid, ti.text, ti.done, ti.sort_order
            FROM todo_items ti
            INNER JOIN todo_lists tl ON tl.id = ti.list_id
            WHERE tl.guid = @listGuid
            ORDER BY ti.sort_order, ti.guid;
            """;
        cmd.Parameters.AddWithValue("@listGuid", listId.ToString("D"));

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

        return results;
    }

    private static TodoItem MapItemRow(SqliteDataReader reader)
    {
        var guid = Guid.Parse(reader.GetString(0));
        var listGuid = Guid.Parse(reader.GetString(1));
        var text = reader.GetString(2);
        var done = !reader.IsDBNull(3) && reader.GetInt64(3) != 0;
        var sortOrder = reader.IsDBNull(4) ? 0 : (int)reader.GetInt64(4);
        return new TodoItem(guid, listGuid, text, done, sortOrder);
    }
}
