<?php

namespace App\Libraries;

use App\Models\Control\TenantRecordModel;
use CodeIgniter\Database\Config as DbConfig;
use CodeIgniter\Database\MigrationRunner;
use Config\Migrations as MigrationsConfig;
use Config\Tenancy as TenancyConfig;
use RuntimeException;

/**
 * Provisions a brand-new hotel: gets its physical database ready, runs
 * every tenant-schema migration against it, records it in the control DB,
 * and inserts the default manager login. Used by both `php spark
 * tenant:create` and Admin\TenantController::create() (the "Onboard
 * Hotel" screen) — one provisioning path, no duplicated logic.
 *
 * Whether the database itself gets CREATEd here or is expected to
 * already exist is controlled by Config\Tenancy::$autoCreateDatabase —
 * see that property's own doc comment for why shared cPanel hosting
 * needs it turned off (the app's DB user there can never have CREATE
 * DATABASE privilege, only access to specific pre-created databases).
 */
class TenantProvisioner
{
    public function __construct(
        private readonly TenancyConfig $tenancy,
        private readonly TenantContext $tenantContext,
    ) {
    }

    /**
     * @return object The newly-inserted control-DB tenant row.
     */
    public function provision(
        string $hotelName,
        string $slug,
        string $adminName,
        string $adminEmail,
        string $adminPassword,
    ): object {
        $dbName = $this->tenancy->dbNamePrefix . $slug;

        $this->ensureDatabaseReady($dbName);
        $this->runTenantMigrations($dbName);

        $tenants = new TenantRecordModel();
        $tenantId = $tenants->insert([
            'hotel_name' => $hotelName,
            'slug'       => $slug,
            'db_name'    => $dbName,
            'db_user'    => $this->tenancy->dbTemplate['username'],
            'status'     => 'active',
        ], true);

        $tenant = $tenants->find($tenantId);

        $this->seedDefaultAdmin($dbName, $adminName, $adminEmail, $adminPassword);

        return $tenant;
    }

    /**
     * The expected physical database name for a not-yet-provisioned
     * slug — surfaced by Admin\TenantController::new() so the "Onboard
     * Hotel" form can show exactly what a cPanel admin needs to have
     * already created (see Config\Tenancy::$autoCreateDatabase) before
     * submitting, rather than them guessing at the naming convention.
     */
    public function expectedDbNameFor(string $slug): string
    {
        return $this->tenancy->dbNamePrefix . $slug;
    }

    private function ensureDatabaseReady(string $dbName): void
    {
        if (! preg_match('/^[a-zA-Z0-9_]+$/', $dbName)) {
            throw new RuntimeException("Refusing to use database with unsafe name: {$dbName}");
        }

        if ($this->tenancy->autoCreateDatabase) {
            // Bootstrap connection with no specific database selected yet
            // — only reachable when the app's DB user genuinely has
            // CREATE DATABASE privilege (local dev, or a VPS/dedicated
            // box this app's own MySQL account administers).
            $bootstrap = DbConfig::connect($this->tenancy->dbTemplate, false);
            $bootstrap->query(
                "CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
            );
            $bootstrap->close();

            return;
        }

        // Shared-hosting path: the database must already exist (an admin
        // created it by hand via cPanel's MySQL Databases tool and
        // granted the app's DB user access to it) and must be genuinely
        // empty — refusing to migrate into a non-empty database is what
        // stops a mistyped/reused slug from silently running tenant
        // migrations on top of a database that already holds real data.
        $bootstrap = DbConfig::connect($this->tenancy->dbTemplate, false);

        $exists = $bootstrap->query(
            'SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?',
            [$dbName],
        )->getRow() !== null;

        if (! $exists) {
            $bootstrap->close();

            throw new RuntimeException(
                "Database \"{$dbName}\" doesn't exist yet. This server is configured for shared hosting " .
                "(Config\\Tenancy::\$autoCreateDatabase is off) — create an empty database with exactly this " .
                "name via cPanel's MySQL Databases tool, grant this app's database user access to it, then try " .
                'provisioning this hotel again.',
            );
        }

        $tableCount = (int) $bootstrap->query(
            'SELECT COUNT(*) AS c FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?',
            [$dbName],
        )->getRow()->c;

        $bootstrap->close();

        if ($tableCount > 0) {
            throw new RuntimeException(
                "Database \"{$dbName}\" already has {$tableCount} table(s) in it — refusing to migrate a " .
                'tenant schema on top of a database that isn\'t empty. If this is meant to be a brand-new ' .
                'hotel, create a fresh empty database in cPanel instead of reusing this one.',
            );
        }
    }

    private function runTenantMigrations(string $dbName): void
    {
        $connection = $this->tenantContext->buildConnectionFor($dbName);

        $runner = new MigrationRunner(config(MigrationsConfig::class), $connection);
        $runner->setNamespace('Tenant');
        $runner->latest('tenant');
    }

    private function seedDefaultAdmin(string $dbName, string $name, string $email, string $password): void
    {
        $connection = $this->tenantContext->buildConnectionFor($dbName);

        $connection->table('users')->insert([
            'full_name'     => $name,
            'email'         => $email,
            'password_hash' => password_hash($password, PASSWORD_DEFAULT),
            'role'          => 'manager',
            'is_active'     => 1,
            'created_at'    => date('Y-m-d H:i:s'),
            'updated_at'    => date('Y-m-d H:i:s'),
        ]);
    }
}
