<?php

namespace App\Libraries;

use Config\Database as DbConfig;
use Config\Tenancy as TenancyConfig;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\ConnectionInterface;
use RuntimeException;

/**
 * Holds the tenant resolved for the current request and lazily builds the
 * database connection for it.
 *
 * This is registered as a shared service (see Config\Services::tenantContext())
 * so it behaves like a per-request singleton: the TenantResolver filter sets
 * the tenant once at the start of the request, and every model/controller
 * downstream that asks for it (directly, or via TenantModel / the tenantDb()
 * service) gets the same resolved tenant and the same open connection.
 */
class TenantContext
{
    private ?object $tenant = null;
    private ?ConnectionInterface $connection = null;

    public function __construct(private readonly TenancyConfig $config)
    {
    }

    public function hasTenant(): bool
    {
        return $this->tenant !== null;
    }

    /**
     * @param object $tenant Row from the control DB `tenants` table
     *                       (id, hotel_name, slug, db_name, db_user, status, ...)
     */
    public function setTenant(object $tenant): void
    {
        $this->tenant     = $tenant;
        $this->connection = null; // force rebuild if the tenant changes mid-process (e.g. CLI loops)
    }

    public function getTenant(): object
    {
        if ($this->tenant === null) {
            throw new RuntimeException(
                'No tenant has been resolved for this request. ' .
                'TenantModel/tenantDb() must not be used before TenantResolver has run.'
            );
        }

        return $this->tenant;
    }

    /**
     * Returns the live DB connection for the resolved tenant, building it
     * (and letting CodeIgniter cache it by config hash) on first use.
     */
    public function getConnection(): ConnectionInterface
    {
        if ($this->connection !== null) {
            return $this->connection;
        }

        $tenant = $this->getTenant();

        $params             = $this->config->dbTemplate;
        $params['database'] = $tenant->db_name;

        $this->connection = DbConfig::connect($params);

        return $this->connection;
    }

    /**
     * Builds (without caching on this instance) a connection to an
     * arbitrary tenant database name. Used by the provisioning command,
     * which needs a connection before any tenant row's context is "current".
     */
    public function buildConnectionFor(string $dbName): BaseConnection
    {
        $params             = $this->config->dbTemplate;
        $params['database'] = $dbName;

        /** @var BaseConnection $connection */
        $connection = DbConfig::connect($params, false);

        return $connection;
    }
}
