<?php

declare(strict_types=1);

use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;

/**
 * Tests for reduced verbosity of QueryException logging when triggered by
 * a constraint violation (SQLSTATE 23xxx). After incident 2026-05-29, the
 * default Laravel error report (full stack trace) caused laravel.log to
 * accumulate 8.7 GB during a webhook storm. Constraint violations are
 * data-validity errors — they need a warning summary, not a stack trace.
 *
 * Ref: docs/incidents/2026-05-29-disk-full-pg-recovery.md §5
 */
it('logs constraint violation (SQLSTATE 23505) as WARNING with sqlstate code, no stack trace', function () {
    Log::spy();

    Route::get('/_test/boom-23505', function () {
        $pdoException = new PDOException('SQLSTATE[23505]: Unique violation: duplicate key value violates unique constraint "uniq_user_email"');
        $pdoException->errorInfo = ['23505', 7, 'Unique violation'];

        throw new QueryException('pgsql', 'INSERT INTO users ...', [], $pdoException);
    });

    /* @phpstan-ignore-next-line method.notFound */
    $this->getJson('/_test/boom-23505');

    // Constraint violation → warning channel, with sqlstate context
    /* @phpstan-ignore-next-line staticMethod.notFound */
    Log::shouldHaveReceived('warning')
        ->withArgs(function ($message, $context) {
            return $message === 'db.constraint_violation'
                && ($context['sqlstate'] ?? '') === '23505';
        })
        ->atLeast()->once();

    // Default behaviour (full error log) is NOT called for constraint violations
    /* @phpstan-ignore-next-line staticMethod.notFound */
    Log::shouldNotHaveReceived('error', [
        Mockery::on(fn ($msg) => $msg === 'db.query_exception'),
    ]);
});

it('still logs non-constraint QueryException (SQLSTATE 42P01) as ERROR with full SQL', function () {
    Log::spy();

    Route::get('/_test/boom-42P01', function () {
        $pdoException = new PDOException('SQLSTATE[42P01]: relation "missing_table" does not exist');
        $pdoException->errorInfo = ['42P01', 7, 'Undefined table'];

        throw new QueryException('pgsql', 'SELECT * FROM missing_table', [], $pdoException);
    });

    /* @phpstan-ignore-next-line method.notFound */
    $this->getJson('/_test/boom-42P01');

    // Non-constraint → default error logging preserved
    /* @phpstan-ignore-next-line staticMethod.notFound */
    Log::shouldHaveReceived('error')
        ->withArgs(function ($message, $context) {
            return $message === 'db.query_exception'
                && isset($context['sql']);
        })
        ->atLeast()->once();
});

it('logs constraint violation (SQLSTATE 23514) for check_constraint as WARNING', function () {
    Log::spy();

    Route::get('/_test/boom-23514', function () {
        $pdoException = new PDOException('SQLSTATE[23514]: Check violation: new row for relation "supplier_projects" violates check constraint "chk_supplier_projects_b1_not_for_sms"');
        $pdoException->errorInfo = ['23514', 7, 'Check violation'];

        throw new QueryException('pgsql', 'INSERT INTO supplier_projects ...', [], $pdoException);
    });

    /* @phpstan-ignore-next-line method.notFound */
    $this->getJson('/_test/boom-23514');

    /* @phpstan-ignore-next-line staticMethod.notFound */
    Log::shouldHaveReceived('warning')
        ->withArgs(function ($message, $context) {
            return $message === 'db.constraint_violation'
                && ($context['sqlstate'] ?? '') === '23514';
        })
        ->atLeast()->once();
});
