namespace KArtSell.Modules.ModelOperations.Features.ApprovalWorkflow;

using Dapper;
using KArtSell.Modules.ModelOperations.Domain.ApprovalWorkflow;
using Npgsql;

public class ApprovalWorkflowSql
{
    private readonly string _connectionString;

    public ApprovalWorkflowSql(string connectionString) => _connectionString = connectionString;

    public async Task<ApprovalProposal?> GetProposalAsync(Guid proposalId, CancellationToken ct = default)
    {
        const string sql = """
            SELECT id, model_id, status, created_by, created_at, justification, effective_at,
                   proposed_at, approved_by, approved_at, approval_notes, activated_by, activated_at,
                   published_at, revision, correlation_id
            FROM model_operations.approval_proposals
            WHERE id = @proposalId
            """;

        using var conn = new NpgsqlConnection(_connectionString);
        return await conn.QueryFirstOrDefaultAsync<ApprovalProposal>(sql, new { proposalId });
    }

    public async Task<List<ApprovalProposal>> ListProposalsAsync(ApprovalStatus? status = null, Guid? modelId = null, int limit = 50, int offset = 0, CancellationToken ct = default)
    {
        const string sql = """
            SELECT id, model_id, status, created_by, created_at, justification, effective_at,
                   proposed_at, approved_by, approved_at, approval_notes, activated_by, activated_at,
                   published_at, revision, correlation_id
            FROM model_operations.approval_proposals
            WHERE (CAST(@status AS VARCHAR) IS NULL OR status = CAST(@status AS VARCHAR))
              AND (@modelId::UUID IS NULL OR model_id = @modelId)
            ORDER BY created_at DESC
            LIMIT @limit OFFSET @offset
            """;

        using var conn = new NpgsqlConnection(_connectionString);
        var proposals = await conn.QueryAsync<ApprovalProposal>(sql, new
        {
            status = status?.ToString().ToUpper(),
            modelId,
            limit,
            offset
        });

        return proposals.ToList();
    }

    public async Task<Guid> InsertProposalAsync(ApprovalProposal proposal, CancellationToken ct = default)
    {
        const string sql = """
            INSERT INTO model_operations.approval_proposals
            (id, model_id, status, created_by, created_at, justification, effective_at,
             published_at, revision, correlation_id)
            VALUES (@id, @modelId, @status, @createdBy, @createdAt, @justification, @effectiveAt::date,
                    @publishedAt, @revision, @correlationId)
            RETURNING id
            """;

        using var conn = new NpgsqlConnection(_connectionString);
        return await conn.QuerySingleAsync<Guid>(sql, new
        {
            proposal.Id,
            proposal.ModelId,
            status = proposal.Status.ToString().ToUpper(),
            proposal.CreatedBy,
            proposal.CreatedAt,
            proposal.Justification,
            // Dapper cannot bind DateOnly directly as an Npgsql parameter value (see TECH_DEBT_REGISTER.md
            // DEBT-017); the identical failure was found and fixed the same way in the now-deleted
            // ApprovalSql.cs (commit 2ccf74c) after it crashed 100% of proposal-creation calls against a
            // real database.
            effectiveAt = proposal.EffectiveAt.ToString("yyyy-MM-dd"),
            proposal.PublishedAt,
            proposal.Revision,
            proposal.CorrelationId
        });
    }

    public async Task UpdateProposalStatusAsync(Guid proposalId, ApprovalStatus newStatus, string? approvedBy = null, string? approvalNotes = null, CancellationToken ct = default)
    {
        const string sql = """
            UPDATE model_operations.approval_proposals
            SET status = @status, approved_by = @approvedBy, approved_at = CASE WHEN @approvedBy IS NOT NULL THEN NOW() ELSE approved_at END,
                approval_notes = @approvalNotes, published_at = NOW(), revision = revision + 1
            WHERE id = @proposalId
            """;

        using var conn = new NpgsqlConnection(_connectionString);
        await conn.ExecuteAsync(sql, new
        {
            proposalId,
            status = newStatus.ToString().ToUpper(),
            approvedBy,
            approvalNotes
        });
    }

    /// <summary>
    /// DEBT-028: activation must NOT go through UpdateProposalStatusAsync — that method's
    /// approvedBy/approvalNotes parameters would overwrite the checker's approved_by/
    /// approval_notes with the activating SRE's identity, and it never touches
    /// activated_by/activated_at at all (those columns exist in the schema but nothing wrote
    /// them). This is a dedicated method so activation only touches activation-specific columns.
    /// </summary>
    public async Task ActivateProposalAsync(Guid proposalId, string activatedBy, CancellationToken ct = default)
    {
        const string sql = """
            UPDATE model_operations.approval_proposals
            SET status = 'ACTIVE', activated_by = @activatedBy, activated_at = NOW(),
                published_at = NOW(), revision = revision + 1
            WHERE id = @proposalId
            """;

        using var conn = new NpgsqlConnection(_connectionString);
        await conn.ExecuteAsync(sql, new { proposalId, activatedBy });
    }

    public async Task<List<ApprovalEvidence>> GetEvidenceForProposalAsync(Guid proposalId, CancellationToken ct = default)
    {
        const string sql = """
            SELECT id, approval_proposal_id, evidence_type, evidence_url, reviewer_comment,
                   published_at, correlation_id
            FROM model_operations.approval_evidence
            WHERE approval_proposal_id = @proposalId
            ORDER BY published_at
            """;

        using var conn = new NpgsqlConnection(_connectionString);
        var evidence = await conn.QueryAsync<ApprovalEvidence>(sql, new { proposalId });
        return evidence.ToList();
    }

    public async Task<Guid> InsertEvidenceAsync(ApprovalEvidence evidence, CancellationToken ct = default)
    {
        const string sql = """
            INSERT INTO model_operations.approval_evidence
            (id, approval_proposal_id, evidence_type, evidence_url, reviewer_comment, published_at, correlation_id)
            VALUES (@id, @proposalId, @type, @url, @comment, @publishedAt, @correlationId)
            RETURNING id
            """;

        using var conn = new NpgsqlConnection(_connectionString);
        return await conn.QuerySingleAsync<Guid>(sql, new
        {
            evidence.Id,
            proposalId = evidence.ApprovalProposalId,
            type = evidence.EvidenceType,
            url = evidence.EvidenceUrl,
            comment = evidence.ReviewerComment,
            evidence.PublishedAt,
            evidence.CorrelationId
        });
    }

    public async Task<Guid> InsertEventAsync(ApprovalEvent evt, CancellationToken ct = default)
    {
        const string sql = """
            INSERT INTO model_operations.approval_events
            (id, approval_proposal_id, event_type, actor_email, event_at, details, published_at, correlation_id)
            VALUES (@id, @proposalId, @type, @email, @at, @details::JSONB, @publishedAt, @correlationId)
            RETURNING id
            """;

        using var conn = new NpgsqlConnection(_connectionString);
        return await conn.QuerySingleAsync<Guid>(sql, new
        {
            evt.Id,
            proposalId = evt.ApprovalProposalId,
            type = evt.EventType,
            email = evt.ActorEmail,
            at = evt.EventAt,
            details = System.Text.Json.JsonSerializer.Serialize(evt.Details ?? new()),
            evt.PublishedAt,
            evt.CorrelationId
        });
    }
}
