<?php

declare(strict_types=1);

namespace Tests\Framework\Database\StoredProcedure;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\ResultInterface;
use App\Framework\Database\StoredProcedure\StoredProcedureDefinition;
use App\Framework\Database\StoredProcedure\StoredProcedureManager;
use Mockery;

beforeEach(function () {
    $this->connection = Mockery::mock(ConnectionInterface::class);
    $this->manager = new StoredProcedureManager($this->connection);
});

afterEach(function () {
    Mockery::close();
});

test('creates a stored procedure', function () {
    $procedure = StoredProcedureDefinition::create('get_user_by_id')
        ->withParameter('user_id', 'INT')
        ->withBody('SELECT * FROM users WHERE id = user_id')
        ->returnsType('TABLE');

    $this->connection->shouldReceive('getPdo->getAttribute')
        ->with(\PDO::ATTR_DRIVER_NAME)
        ->andReturn('mysql');

    $this->connection->shouldReceive('execute')
        ->with(Mockery::pattern('/CREATE PROCEDURE/'), [])
        ->once()
        ->andReturn(1);

    $result = $this->manager->createProcedure($procedure);

    expect($result)->toBeTrue();
});

test('drops a stored procedure', function () {
    $this->connection->shouldReceive('getPdo->getAttribute')
        ->with(\PDO::ATTR_DRIVER_NAME)
        ->andReturn('mysql');

    $this->connection->shouldReceive('execute')
        ->with('DROP PROCEDURE IF EXISTS `get_user_by_id`', [])
        ->once()
        ->andReturn(1);

    $result = $this->manager->dropProcedure('get_user_by_id');

    expect($result)->toBeTrue();
});

test('checks if a stored procedure exists', function () {
    $this->connection->shouldReceive('getPdo->getAttribute')
        ->with(\PDO::ATTR_DRIVER_NAME)
        ->andReturn('mysql');

    $this->connection->shouldReceive('queryScalar')
        ->with(Mockery::pattern('/INFORMATION_SCHEMA.ROUTINES/'), ['get_user_by_id'])
        ->once()
        ->andReturn(1);

    $result = $this->manager->procedureExists('get_user_by_id');

    expect($result)->toBeTrue();
});

test('executes a stored procedure with parameters', function () {
    $this->connection->shouldReceive('getPdo->getAttribute')
        ->with(\PDO::ATTR_DRIVER_NAME)
        ->andReturn('mysql');

    $mockResult = Mockery::mock(ResultInterface::class);
    $mockResult->shouldReceive('fetchAll')->andReturn([
        ['id' => 1, 'name' => 'John Doe', 'email' => 'john@example.com'],
    ]);

    $this->connection->shouldReceive('query')
        ->with('CALL get_user_by_id(?)', [42])
        ->once()
        ->andReturn($mockResult);

    $result = $this->manager->executeProcedure('get_user_by_id', [42]);

    expect($result)->toBeInstanceOf(ResultInterface::class);
    expect($result->fetchAll())->toHaveCount(1);
    expect($result->fetchAll()[0]['name'])->toBe('John Doe');
});

test('executes a stored function with parameters', function () {
    $this->connection->shouldReceive('getPdo->getAttribute')
        ->with(\PDO::ATTR_DRIVER_NAME)
        ->andReturn('mysql');

    $this->connection->shouldReceive('queryScalar')
        ->with('SELECT calculate_order_total(?, ?)', [123, true])
        ->once()
        ->andReturn(99.99);

    $result = $this->manager->executeFunction('calculate_order_total', [123, true]);

    expect($result)->toBe(99.99);
});

test('lists all stored procedures', function () {
    $this->connection->shouldReceive('getPdo->getAttribute')
        ->with(\PDO::ATTR_DRIVER_NAME)
        ->andReturn('mysql');

    $mockResult = Mockery::mock(ResultInterface::class);
    $mockResult->shouldReceive('fetchAll')->andReturn([
        ['ROUTINE_NAME' => 'get_user_by_id', 'ROUTINE_TYPE' => 'PROCEDURE'],
        ['ROUTINE_NAME' => 'calculate_order_total', 'ROUTINE_TYPE' => 'FUNCTION'],
    ]);

    $this->connection->shouldReceive('query')
        ->with(Mockery::pattern('/INFORMATION_SCHEMA.ROUTINES/'), [])
        ->once()
        ->andReturn($mockResult);

    $procedures = $this->manager->listProcedures();

    expect($procedures)->toHaveCount(2);
    expect($procedures[0]['name'])->toBe('get_user_by_id');
    expect($procedures[0]['type'])->toBe('PROCEDURE');
    expect($procedures[1]['name'])->toBe('calculate_order_total');
    expect($procedures[1]['type'])->toBe('FUNCTION');
});

test('gets procedure definition', function () {
    $this->connection->shouldReceive('getPdo->getAttribute')
        ->with(\PDO::ATTR_DRIVER_NAME)
        ->andReturn('mysql');

    $mockResult = Mockery::mock(ResultInterface::class);
    $mockResult->shouldReceive('fetchOne')->andReturn([
        'ROUTINE_NAME' => 'get_user_by_id',
        'ROUTINE_TYPE' => 'PROCEDURE',
        'ROUTINE_DEFINITION' => 'SELECT * FROM users WHERE id = user_id',
        'DTD_IDENTIFIER' => null,
        'PARAMETER_STYLE' => 'SQL',
    ]);

    $this->connection->shouldReceive('query')
        ->with(Mockery::pattern('/INFORMATION_SCHEMA.ROUTINES/'), ['get_user_by_id'])
        ->once()
        ->andReturn($mockResult);

    $paramResult = Mockery::mock(ResultInterface::class);
    $paramResult->shouldReceive('fetchAll')->andReturn([
        ['PARAMETER_NAME' => 'user_id', 'DATA_TYPE' => 'INT', 'PARAMETER_MODE' => 'IN'],
    ]);

    $this->connection->shouldReceive('query')
        ->with(Mockery::pattern('/INFORMATION_SCHEMA.PARAMETERS/'), ['get_user_by_id'])
        ->once()
        ->andReturn($paramResult);

    $definition = $this->manager->getProcedureDefinition('get_user_by_id');

    expect($definition)->toBeInstanceOf(StoredProcedureDefinition::class);
    expect($definition->getName())->toBe('get_user_by_id');
    expect($definition->getBody())->toBe('SELECT * FROM users WHERE id = user_id');
    expect($definition->getParameters())->toHaveCount(1);
    expect($definition->getParameters()[0]['name'])->toBe('user_id');
    expect($definition->getParameters()[0]['type'])->toBe('INT');
});

test('handles unsupported database driver', function () {
    $this->connection->shouldReceive('getPdo->getAttribute')
        ->with(\PDO::ATTR_DRIVER_NAME)
        ->andReturn('unsupported_driver');

    expect(fn () => $this->manager->procedureExists('test_procedure'))
        ->toThrow(\InvalidArgumentException::class, 'Unsupported database driver: unsupported_driver');
});
