using System.Text;
using Dapper;
using SIGCM2.Application.Abstractions.Persistence;
using SIGCM2.Application.Common;
using SIGCM2.Domain.Entities;

namespace SIGCM2.Infrastructure.Persistence;

public sealed class MedioRepository : IMedioRepository
{
    private readonly SqlConnectionFactory _connectionFactory;

    public MedioRepository(SqlConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public async Task<int> AddAsync(Medio m, CancellationToken ct = default)
    {
        // DF handles: Activo (1), FechaCreacion (SYSUTCDATETIME()).
        const string sql = """
            INSERT INTO dbo.Medio (Codigo, Nombre, Tipo, PlataformaEmpresaId)
            OUTPUT INSERTED.Id
            VALUES (@Codigo, @Nombre, @Tipo, @PlataformaEmpresaId)
            """;

        await using var connection = _connectionFactory.CreateConnection();
        await connection.OpenAsync(ct);

        return await connection.ExecuteScalarAsync<int>(sql, new
        {
            m.Codigo,
            m.Nombre,
            Tipo = (int)m.Tipo,
            m.PlataformaEmpresaId,
        });
    }

    public async Task<Medio?> GetByIdAsync(int id, CancellationToken ct = default)
    {
        const string sql = """
            SELECT Id, Codigo, Nombre, Tipo, PlataformaEmpresaId, Activo, FechaCreacion, FechaModificacion
            FROM dbo.Medio
            WHERE Id = @Id
            """;

        await using var connection = _connectionFactory.CreateConnection();
        await connection.OpenAsync(ct);

        var row = await connection.QuerySingleOrDefaultAsync<MedioRow>(sql, new { Id = id });
        return row is null ? null : MapRow(row);
    }

    public async Task<bool> ExistsByCodigoAsync(string codigo, CancellationToken ct = default)
    {
        const string sql = """
            SELECT COUNT(1) FROM dbo.Medio WHERE Codigo = @Codigo
            """;

        await using var connection = _connectionFactory.CreateConnection();
        await connection.OpenAsync(ct);

        var count = await connection.ExecuteScalarAsync<int>(sql, new { Codigo = codigo });
        return count > 0;
    }

    public async Task UpdateAsync(Medio m, CancellationToken ct = default)
    {
        const string sql = """
            UPDATE dbo.Medio
            SET Nombre              = @Nombre,
                Tipo                = @Tipo,
                PlataformaEmpresaId = @PlataformaEmpresaId,
                Activo              = @Activo,
                FechaModificacion   = @FechaModificacion
            WHERE Id = @Id
            """;

        await using var connection = _connectionFactory.CreateConnection();
        await connection.OpenAsync(ct);

        await connection.ExecuteAsync(sql, new
        {
            m.Nombre,
            Tipo = (int)m.Tipo,
            m.PlataformaEmpresaId,
            m.Activo,
            FechaModificacion = m.FechaModificacion,
            m.Id,
        });
    }

    public async Task<PagedResult<Medio>> GetPagedAsync(MediosQuery q, CancellationToken ct = default)
    {
        var page = Math.Max(1, q.Page);
        var pageSize = Math.Clamp(q.PageSize, 1, 100);
        var offset = (page - 1) * pageSize;

        var where = new StringBuilder("WHERE 1=1");
        var parameters = new DynamicParameters();
        parameters.Add("PageSize", pageSize);
        parameters.Add("Offset", offset);

        if (q.Activo.HasValue)
        {
            where.Append(" AND Activo = @Activo");
            parameters.Add("Activo", q.Activo.Value ? 1 : 0);
        }

        if (q.Tipo.HasValue)
        {
            where.Append(" AND Tipo = @Tipo");
            parameters.Add("Tipo", (int)q.Tipo.Value);
        }

        if (!string.IsNullOrWhiteSpace(q.Search))
        {
            where.Append(" AND (Codigo LIKE @Search OR Nombre LIKE @Search)");
            parameters.Add("Search", $"%{q.Search}%");
        }

        var sql = $"""
            SELECT
                Id, Codigo, Nombre, Tipo, PlataformaEmpresaId, Activo, FechaCreacion, FechaModificacion,
                COUNT(*) OVER() AS TotalCount
            FROM dbo.Medio
            {where}
            ORDER BY Codigo
            OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY
            """;

        await using var connection = _connectionFactory.CreateConnection();
        await connection.OpenAsync(ct);

        var rows = await connection.QueryAsync<MedioPagedRow>(sql, parameters);
        var list = rows.ToList();

        var total = list.Count > 0 ? list[0].TotalCount : 0;
        var items = list.Select(r => MapRow(r)).ToList();

        return new PagedResult<Medio>(items, page, pageSize, total);
    }

    // ── mapping ───────────────────────────────────────────────────────────────

    private static Medio MapRow(MedioRow r)
        => new(
            id: r.Id,
            codigo: r.Codigo,
            nombre: r.Nombre,
            tipo: (TipoMedio)r.Tipo,
            plataformaEmpresaId: r.PlataformaEmpresaId,
            activo: r.Activo,
            fechaCreacion: r.FechaCreacion,
            fechaModificacion: r.FechaModificacion);

    private static Medio MapRow(MedioPagedRow r)
        => new(
            id: r.Id,
            codigo: r.Codigo,
            nombre: r.Nombre,
            tipo: (TipoMedio)r.Tipo,
            plataformaEmpresaId: r.PlataformaEmpresaId,
            activo: r.Activo,
            fechaCreacion: r.FechaCreacion,
            fechaModificacion: r.FechaModificacion);

    private sealed record MedioRow(
        int Id,
        string Codigo,
        string Nombre,
        byte Tipo,
        int? PlataformaEmpresaId,
        bool Activo,
        DateTime FechaCreacion,
        DateTime? FechaModificacion);

    private sealed record MedioPagedRow(
        int Id,
        string Codigo,
        string Nombre,
        byte Tipo,
        int? PlataformaEmpresaId,
        bool Activo,
        DateTime FechaCreacion,
        DateTime? FechaModificacion,
        int TotalCount);
}
