<?php

namespace App\Domains\Integration\Services;

use App\Domains\Client\Models\Client;
use App\Domains\Integration\Models\ClientIntegration;
use App\Domains\Integration\Models\Integration;
use App\Domains\Tenant\Models\Tenant;
use Exception;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;

abstract class BaseIntegrationService
{
    /**
     * The unique code of the integration.
     */
    protected string $integrationCode;

    /**
     * The current tenant code.
     */
    protected string $tenantCode;

    protected ?Tenant $tenant = null;

    protected ?Client $clientContext = null;

    /**
     * The integration model instance.
     */
    protected ?Integration $integration = null;

    /**
     * The client-owned integration configuration.
     */
    protected ?ClientIntegration $clientIntegration = null;

    /**
     * Set the integration code.
     *
     * @return $this
     */
    public function setIntegrationCode(string $integrationCode): self
    {
        $this->integrationCode = $integrationCode;

        return $this;
    }

    /**
     * Get the integration code.
     */
    public function getIntegrationCode(): string
    {
        return $this->integrationCode;
    }

    /**
     * Set the tenant code and load the integration models.
     *
     * @return $this
     *
     * @throws Exception
     */
    public function forTenant(string $tenantCode): self
    {
        $this->tenantCode = $tenantCode;
        $this->tenant = Tenant::query()->with('client')->where('codigo', $tenantCode)->firstOrFail();
        $this->clientContext = $this->tenant->client;
        $this->loadIntegration();

        return $this;
    }

    public function forClient(Client|string $client): self
    {
        $this->clientContext = $client instanceof Client
            ? $client
            : Client::query()->where('code', $client)->firstOrFail();
        $this->tenant = null;
        $this->loadIntegration();

        return $this;
    }

    /**
     * Load the integration definition and its client-owned configuration.
     *
     * @throws Exception
     */
    protected function loadIntegration(): void
    {
        if (empty($this->integrationCode)) {
            throw new Exception('Integration code is not set.');
        }

        $this->integration = Integration::where('integration_code', $this->integrationCode)->first();
        if (! $this->integration) {
            throw new Exception("Integration with code '{$this->integrationCode}' not found.");
        }

        if (! $this->clientContext) {
            throw new Exception('Client context is not set.');
        }

        $this->clientIntegration = ClientIntegration::where('client_id', $this->clientContext->id)
            ->where('integration_code', $this->integrationCode)
            ->first();

        if (! $this->clientIntegration && $this->integration->requires_client_configuration) {
            throw new Exception("Client '{$this->clientContext->code}' does not have integration '{$this->integrationCode}' configured.");
        }
    }

    /**
     * Build the request URL.
     *
     * @throws Exception
     */
    public function getUrl(string $path = ''): string
    {
        if (! $this->integration) {
            throw new Exception('Integration is not loaded. Call forTenant() or forClient() first.');
        }

        $baseUrl = rtrim($this->integration->url, '/');
        $path = ltrim($path, '/');

        return $path !== '' ? "{$baseUrl}/{$path}" : $baseUrl;
    }

    /**
     * Get an integration setting from the client-owned configuration.
     */
    protected function getIntegrationSetting(string $key, mixed $default = null): mixed
    {
        if (! $this->clientIntegration || ! $this->clientIntegration->integration_data) {
            return $default;
        }

        return $this->clientIntegration->integration_data[$key] ?? $default;
    }

    /**
     * Get a pre-configured HTTP client builder.
     *
     * @throws Exception
     */
    public function client(): PendingRequest
    {
        return Http::baseUrl($this->getUrl())
            ->withHeaders($this->getHeaders());
    }

    /**
     * Get the headers for the integration.
     */
    abstract public function getHeaders(): array;

    /**
     * Hook called after the integration is configured for the client.
     * Can be used to validate credentials or perform initial setups.
     * Throw an Exception on failure.
     */
    public function onSetup(): void
    {
        // Override in child classes if needed
    }
}
