<?php

declare(strict_types=1);

namespace App\Services\Settings;

use App\Models\ManagedEnvironment;
use App\Models\TenantSetting;
use App\Models\Workspace;
use App\Models\WorkspaceSetting;
use App\Support\Settings\SettingDefinition;
use App\Support\Settings\SettingsRegistry;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class SettingsResolver
{
    /**
     * @var array<string, array{domain: string, key: string, value: mixed, source: 'system_default'|'workspace_override'|'tenant_override', system_default: mixed, workspace_value: mixed, tenant_value: mixed}>
     */
    private array $resolved = [];

    public function __construct(private SettingsRegistry $registry) {}

    /**
     * @return array{domain: string, key: string, value: mixed, source: 'system_default'|'workspace_override'|'tenant_override', system_default: mixed, workspace_value: mixed, tenant_value: mixed}
     */
    public function resolveDetailed(Workspace $workspace, string $domain, string $key, ?ManagedEnvironment $tenant = null): array
    {
        if ($tenant instanceof ManagedEnvironment) {
            $this->assertTenantBelongsToWorkspace($workspace, $tenant);
        }

        $cacheKey = $this->cacheKey($workspace, $domain, $key, $tenant);

        if (isset($this->resolved[$cacheKey])) {
            return $this->resolved[$cacheKey];
        }

        $definition = $this->registry->require($domain, $key);

        $workspaceValue = $this->workspaceOverrideValue($workspace, $domain, $key);
        $tenantValue = $tenant instanceof ManagedEnvironment
            ? $this->tenantOverrideValue($workspace, $tenant, $domain, $key)
            : null;

        $source = 'system_default';
        $rawValue = $definition->systemDefault;

        if ($workspaceValue !== null) {
            $source = 'workspace_override';
            $rawValue = $workspaceValue;
        }

        if ($tenantValue !== null) {
            $source = 'tenant_override';
            $rawValue = $tenantValue;
        }

        $effectiveValue = $this->mergeWithDefault($definition, $rawValue);

        return $this->resolved[$cacheKey] = [
            'domain' => $domain,
            'key' => $key,
            'value' => $effectiveValue,
            'source' => $source,
            'system_default' => $definition->systemDefault,
            'workspace_value' => $workspaceValue,
            'tenant_value' => $tenantValue,
        ];
    }

    public function resolveValue(Workspace $workspace, string $domain, string $key, ?ManagedEnvironment $tenant = null): mixed
    {
        return $this->resolveDetailed($workspace, $domain, $key, $tenant)['value'];
    }

    public function clearCache(): void
    {
        $this->resolved = [];
    }

    /**
     * For JSON settings that store partial overrides (e.g. SLA days with only
     * some severities set), merge the stored partial with the system default
     * so consumers always receive a complete value.
     */
    private function mergeWithDefault(SettingDefinition $definition, mixed $value): mixed
    {
        if ($definition->type !== 'json') {
            return $value;
        }

        if (! is_array($value) || ! is_array($definition->systemDefault)) {
            return $value;
        }

        return array_replace($definition->systemDefault, $value);
    }

    private function workspaceOverrideValue(Workspace $workspace, string $domain, string $key): mixed
    {
        $setting = WorkspaceSetting::query()
            ->where('workspace_id', (int) $workspace->getKey())
            ->where('domain', $domain)
            ->where('key', $key)
            ->first(['value']);

        if (! $setting instanceof WorkspaceSetting) {
            return null;
        }

        return $this->decodeStoredValue($setting->getAttribute('value'));
    }

    private function tenantOverrideValue(Workspace $workspace, ManagedEnvironment $tenant, string $domain, string $key): mixed
    {
        $setting = TenantSetting::query()
            ->where('workspace_id', (int) $workspace->getKey())
            ->where('managed_environment_id', (int) $tenant->getKey())
            ->where('domain', $domain)
            ->where('key', $key)
            ->first(['value']);

        if (! $setting instanceof TenantSetting) {
            return null;
        }

        return $this->decodeStoredValue($setting->getAttribute('value'));
    }

    private function decodeStoredValue(mixed $value): mixed
    {
        if (! is_string($value)) {
            return $value;
        }

        $decoded = json_decode($value, true);

        return json_last_error() === JSON_ERROR_NONE ? $decoded : $value;
    }

    private function assertTenantBelongsToWorkspace(Workspace $workspace, ManagedEnvironment $tenant): void
    {
        if ((int) $tenant->workspace_id !== (int) $workspace->getKey()) {
            throw new NotFoundHttpException('ManagedEnvironment is outside the selected workspace scope.');
        }
    }

    private function cacheKey(Workspace $workspace, string $domain, string $key, ?ManagedEnvironment $tenant): string
    {
        return implode(':', [
            (string) $workspace->getKey(),
            (string) ($tenant?->getKey() ?? 0),
            $domain,
            $key,
        ]);
    }
}
