<?php

namespace Tests;

use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Events\TenancyInitialized;

/**
 * Handles SQLite test database creation and connection configuration.
 * Single responsibility: database files and connections only.
 */
trait UsesDatabase
{
    /**
     * Whether the test persists central records (real tenant, password broker).
     * The central connection itself is always available - see setUpDatabases().
     */
    protected bool $needsMasterDatabase = true;
    protected bool $needsTenantDatabase = true;

    private array $sqliteTestFiles = [];

    private const TYPE_MASTER = 'master';
    private const TYPE_TENANT = 'tenant';

    protected function setUpDatabases(): void
    {
        // The central schema is always provisioned, even for tenant-only tests: tenant code
        // reads CentralConnection models (e.g. TenantHiddenField via the admin view resources)
        // and without it those queries hit the empty :memory: default connection and fail.
        $this->setUpDatabase(self::TYPE_MASTER);

        if ($this->needsMasterDatabase) {
            config(['auth.passwords.users.connection' => self::TYPE_MASTER . '-test']);
        }

        if ($this->needsTenantDatabase) {
            $this->setUpDatabase(self::TYPE_TENANT);
            $this->disableDatabaseTenancyBootstrapper();
            $this->registerTenancyConnectionListener();
        }
    }

    private function setUpDatabase(string $type): void
    {
        $templatePath = $this->getTemplatePath($type);
        $this->assertTemplateExists($templatePath);

        $testPath = $this->generateTestPath($type);
        copy($templatePath, $testPath);

        $this->sqliteTestFiles[$type] = $testPath;
        $this->configureConnection($type, $testPath);

        if ($type === self::TYPE_TENANT) {
            DB::purge(self::TYPE_TENANT . '-test');
        }
    }

    private function configureConnection(string $type, string $filePath): void
    {
        Config::set("database.connections.{$type}-test", [
            'driver' => 'sqlite',
            'database' => $filePath,
            'foreign_key_constraints' => true,
        ]);

        match ($type) {
            self::TYPE_MASTER => Config::set('tenancy.database.central_connection', self::TYPE_MASTER . '-test'),
            self::TYPE_TENANT => Config::set('database.default', self::TYPE_TENANT . '-test'),
        };
    }

    private function disableDatabaseTenancyBootstrapper(): void
    {
        $bootstrappers = array_filter(
            Config::get('tenancy.bootstrappers', []),
            fn ($b) => $b !== DatabaseTenancyBootstrapper::class
        );
        Config::set('tenancy.bootstrappers', $bootstrappers);
    }

    private function registerTenancyConnectionListener(): void
    {
        Event::listen(TenancyInitialized::class, function () {
            if (isset($this->sqliteTestFiles[self::TYPE_TENANT])) {
                $this->configureConnection(
                    self::TYPE_TENANT,
                    $this->sqliteTestFiles[self::TYPE_TENANT]
                );
                DB::purge(self::TYPE_TENANT . '-test');
            }
        });
    }

    protected function tearDownDatabase(): void
    {
        foreach ($this->sqliteTestFiles as $filePath) {
            if (file_exists($filePath)) {
                @unlink($filePath);
            }
        }
        $this->sqliteTestFiles = [];
    }

    private function getTemplatePath(string $type): string
    {
        return base_path("database/testing/{$type}-template.sqlite");
    }

    private function generateTestPath(string $type): string
    {
        return base_path("database/testing/{$type}-test-" . uniqid('', true) . '.sqlite');
    }

    private function assertTemplateExists(string $path): void
    {
        if (!file_exists($path)) {
            throw new \RuntimeException(
                "Template DB not found at {$path}. Run: php artisan testing:prepare-databases"
            );
        }
    }
}
