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

namespace Journal.Core.Repositories;

public sealed class SqliteConversationRepository(IDatabaseSessionService session) : IConversationRepository
{
    private readonly IDatabaseSessionService _session = session;

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

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

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

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

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

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

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

    public void AddMessage(ConversationMessage message)
    {
        ArgumentNullException.ThrowIfNull(message);
        var conn = _session.GetConnection();
        InsertMessage(conn, message);

        // Touch conversation updated_at
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "UPDATE conversations SET updated_at = @now WHERE guid = @guid;";
        cmd.Parameters.AddWithValue("@now", DateTimeOffset.Now.ToString("O"));
        cmd.Parameters.AddWithValue("@guid", message.ConversationId.ToString("D"));
        cmd.ExecuteNonQuery();
    }

    public List<ConversationMessage> GetMessages(Guid conversationId)
    {
        var conn = _session.GetConnection();
        return ReadMessages(conn, conversationId);
    }

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

    private static void Insert(SqliteConnection conn, Conversation c)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            INSERT INTO conversations (guid, title, created_at, updated_at)
            VALUES (@guid, @title, @createdAt, @updatedAt);
            """;
        cmd.Parameters.AddWithValue("@guid", c.Id.ToString("D"));
        cmd.Parameters.AddWithValue("@title", c.Title);
        cmd.Parameters.AddWithValue("@createdAt", c.CreatedAt.ToString("O"));
        cmd.Parameters.AddWithValue("@updatedAt", c.UpdatedAt.ToString("O"));
        cmd.ExecuteNonQuery();
    }

    private static void UpdateRow(SqliteConnection conn, Conversation c)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            UPDATE conversations SET title = @title, updated_at = @updatedAt
            WHERE guid = @guid;
            """;
        cmd.Parameters.AddWithValue("@guid", c.Id.ToString("D"));
        cmd.Parameters.AddWithValue("@title", c.Title);
        cmd.Parameters.AddWithValue("@updatedAt", c.UpdatedAt.ToString("O"));
        cmd.ExecuteNonQuery();
    }

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

        // Get the row id for cascade delete of messages
        var rowId = GetRowId(conn, id);
        if (rowId.HasValue)
        {
            using var delMsgs = conn.CreateCommand();
            delMsgs.CommandText = "DELETE FROM conversation_messages WHERE conversation_id = @id;";
            delMsgs.Parameters.AddWithValue("@id", rowId.Value);
            delMsgs.ExecuteNonQuery();
        }

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

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

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

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

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

    private static List<Conversation> ReadAll(SqliteConnection conn)
    {
        var results = new List<Conversation>();
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "SELECT guid, title, created_at, updated_at FROM conversations ORDER BY updated_at DESC;";

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

        return results;
    }

    private static void InsertMessage(SqliteConnection conn, ConversationMessage m)
    {
        var convRowId = GetRowId(conn, m.ConversationId)
            ?? throw new InvalidOperationException($"Conversation {m.ConversationId} not found");

        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            INSERT INTO conversation_messages (guid, conversation_id, role, text, created_at)
            VALUES (@guid, @conversationId, @role, @text, @createdAt);
            """;
        cmd.Parameters.AddWithValue("@guid", m.Id.ToString("D"));
        cmd.Parameters.AddWithValue("@conversationId", convRowId);
        cmd.Parameters.AddWithValue("@role", m.Role);
        cmd.Parameters.AddWithValue("@text", m.Text);
        cmd.Parameters.AddWithValue("@createdAt", m.CreatedAt.ToString("O"));
        cmd.ExecuteNonQuery();
    }

    private static List<ConversationMessage> ReadMessages(SqliteConnection conn, Guid conversationId)
    {
        var rowId = GetRowId(conn, conversationId);
        if (!rowId.HasValue)
            return [];

        var results = new List<ConversationMessage>();
        using var cmd = conn.CreateCommand();
        cmd.CommandText = """
            SELECT guid, role, text, created_at
            FROM conversation_messages
            WHERE conversation_id = @conversationId
            ORDER BY created_at ASC;
            """;
        cmd.Parameters.AddWithValue("@conversationId", rowId.Value);

        using var reader = cmd.ExecuteReader();
        while (reader.Read())
            results.Add(MapMessage(reader, conversationId));

        return results;
    }

    private static Conversation MapConversation(SqliteDataReader reader)
    {
        var guid = Guid.Parse(reader.GetString(0));
        var title = reader.GetString(1);
        var createdAt = reader.IsDBNull(2) ? DateTimeOffset.MinValue : DateTimeOffset.Parse(reader.GetString(2));
        var updatedAt = reader.IsDBNull(3) ? createdAt : DateTimeOffset.Parse(reader.GetString(3));
        return new Conversation(guid, title, createdAt, updatedAt);
    }

    private static ConversationMessage MapMessage(SqliteDataReader reader, Guid conversationId)
    {
        var guid = Guid.Parse(reader.GetString(0));
        var role = reader.GetString(1);
        var text = reader.IsDBNull(2) ? "" : reader.GetString(2);
        var createdAt = reader.IsDBNull(3) ? DateTimeOffset.MinValue : DateTimeOffset.Parse(reader.GetString(3));
        return new ConversationMessage(guid, conversationId, role, text, createdAt);
    }
}
