<?php

namespace Tests\Feature;

use App\Http\Controllers\ChatController;
use Illuminate\Database\QueryException;
use PDOException;
use PHPUnit\Framework\Attributes\DataProvider;
use ReflectionMethod;
use RuntimeException;
use Tests\TestCase;
use Throwable;

/**
 * A database failure does not arrive in the chat as a database failure.
 *
 * Koushik saw this twice in one conversation on 16 Sep:
 *
 *   "That step failed: SQLSTATE[22001]: String data, right truncated: 1406
 *    Data too long for column 'created_by_message_id' at row 1 (Connection:
 *    mysql, Host: 127.0.0.1, Port: 3306, D."
 *
 * The driver, the host, the port and the beginning of the database name, in a
 * chat bubble, to someone building an ad campaign. It stopped mid-word only
 * because the fallback truncates at 160 characters.
 *
 * The column had already been widened by a migration the day before; arb-dev
 * had not run it. So the disclosure outlived the defect that surfaced it, and
 * would have outlived the next one: the logs from that period still carry
 * eleven QueryExceptions and eight PDOExceptions.
 */
class ADatabaseErrorIsNotShownToTheBuyerTest extends TestCase
{
    private function readable(Throwable $e): string
    {
        $method = new ReflectionMethod(ChatController::class, 'readableFailure');
        $method->setAccessible(true);

        return $method->invoke(app(ChatController::class), $e);
    }

    private function queryException(): QueryException
    {
        return new QueryException(
            'mysql',
            'insert into `conversation_artifacts` (`conversation_id`) values (?)',
            [],
            new PDOException(
                "SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'created_by_message_id' at row 1"
            ),
        );
    }

    /** @return list<list<string>> */
    public static function secrets(): array
    {
        return [
            ['SQLSTATE'],
            ['mysql'],
            ['127.0.0.1'],
            ['Port'],
            ['conversation_artifacts'],
            ['created_by_message_id'],
            ['insert into'],
        ];
    }

    #[DataProvider('secrets')]
    public function test_nothing_internal_reaches_the_buyer(string $secret): void
    {
        $this->assertStringNotContainsString($secret, $this->readable($this->queryException()));
    }

    /** And what they do get says the state of their work and who can help. */
    public function test_it_says_nothing_changed_and_who_to_ask(): void
    {
        $said = $this->readable($this->queryException());

        $this->assertStringContainsString('nothing was changed', $said);
        $this->assertStringContainsString('administrator', $said);
    }

    /** A bare PDOException is covered too, not only the wrapped kind. */
    public function test_a_bare_pdo_exception_is_covered(): void
    {
        $this->assertStringNotContainsString(
            'SQLSTATE',
            $this->readable(new PDOException('SQLSTATE[HY000] [2002] Connection refused')),
        );
    }

    /** A provider failure still gets its own specific advice. */
    public function test_other_failures_keep_their_own_wording(): void
    {
        $this->assertStringContainsString(
            'busy',
            $this->readable(new RuntimeException('The AI provider is overloaded')),
        );
    }
}
