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 SeccionRepository : ISeccionRepository
{
    private readonly SqlConnectionFactory _connectionFactory;

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

    public async Task<int> AddAsync(Seccion s, CancellationToken ct = default)
    {
        // DF handles: Activo (1), FechaCreacion (SYSUTCDATETIME()).
        // FK_Seccion_Medio: if MedioId does not exist, SQL Server raises FK violation — let it bubble.
        const string sql = """
            INSERT INTO dbo.Seccion (MedioId, Codigo, Nombre, Tipo)
            OUTPUT INSERTED.Id
            VALUES (@MedioId, @Codigo, @Nombre, @Tipo)
            """;

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

        return await connection.ExecuteScalarAsync<int>(sql, new
        {
            s.MedioId,
            s.Codigo,
            s.Nombre,
            s.Tipo,
        });
    }

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

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

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

    public async Task<bool> ExistsByCodigoInMedioAsync(int medioId, string codigo, CancellationToken ct = default)
    {
        const string sql = """
            SELECT COUNT(1) FROM dbo.Seccion WHERE MedioId = @MedioId AND Codigo = @Codigo
            """;

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

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

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

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

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

    public async Task<PagedResult<Seccion>> GetPagedAsync(SeccionesQuery 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.MedioId.HasValue)
        {
            where.Append(" AND MedioId = @MedioId");
            parameters.Add("MedioId", q.MedioId.Value);
        }

        if (!string.IsNullOrWhiteSpace(q.Tipo))
        {
            where.Append(" AND Tipo = @Tipo");
            parameters.Add("Tipo", q.Tipo);
        }

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

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

        // ADM-001: filter only Seccion.Activo; Medio.Activo check is left to Application/UI layer.
        // Joining Medio to filter on m.Activo would affect performance for large catalogs and is
        // not required by the current specs. REQ-SEC-003 (Deactivate Medio hides Secciones) is
        // enforced at the Application handler level, not the query level.
        var sql = $"""
            SELECT
                Id, MedioId, Codigo, Nombre, Tipo, Activo, FechaCreacion, FechaModificacion,
                COUNT(*) OVER() AS TotalCount
            FROM dbo.Seccion
            {where}
            ORDER BY MedioId, Codigo
            OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY
            """;

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

        var rows = await connection.QueryAsync<SeccionPagedRow>(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<Seccion>(items, page, pageSize, total);
    }

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

    private static Seccion MapRow(SeccionRow r)
        => new(
            id: r.Id,
            medioId: r.MedioId,
            codigo: r.Codigo,
            nombre: r.Nombre,
            tipo: r.Tipo,
            activo: r.Activo,
            fechaCreacion: r.FechaCreacion,
            fechaModificacion: r.FechaModificacion);

    private static Seccion MapRow(SeccionPagedRow r)
        => new(
            id: r.Id,
            medioId: r.MedioId,
            codigo: r.Codigo,
            nombre: r.Nombre,
            tipo: r.Tipo,
            activo: r.Activo,
            fechaCreacion: r.FechaCreacion,
            fechaModificacion: r.FechaModificacion);

    private sealed record SeccionRow(
        int Id,
        int MedioId,
        string Codigo,
        string Nombre,
        string Tipo,
        bool Activo,
        DateTime FechaCreacion,
        DateTime? FechaModificacion);

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