<?php

namespace MintyPHP\Module\Security\Service;

use MintyPHP\Service\Settings\SettingsCryptoGatewayInterface;
use MintyPHP\Service\Settings\SettingsMetadataGateway;

/**
 * Global Business Central connection settings for the Security module.
 *
 * Standalone, intentionally thin: a single global connection stored in the core
 * `settings` table (GR-CORE-011). Secrets are encrypted via SettingsCryptoGateway
 * (GR-SEC-005). Setting keys are prefixed with 'security.' to avoid collisions
 * with the helpdesk module's own BC settings.
 *
 * Per-tenant connection overrides are deliberately out of scope for v1.
 *
 * @api Consumed by the Security settings page and SecurityBcGateway.
 */
class SecurityBcSettingsGateway
{
    public const KEY_AUTH_MODE = 'security.bc_auth_mode';
    public const KEY_ODATA_BASE_URL = 'security.bc_odata_base_url';
    public const KEY_COMPANY_NAME = 'security.bc_company_name';
    public const KEY_BASIC_USER = 'security.bc_basic_user';
    public const KEY_BASIC_PASSWORD_ENC = 'security.bc_basic_password_enc';
    public const KEY_OAUTH_TENANT_ID = 'security.bc_oauth_tenant_id';
    public const KEY_OAUTH_CLIENT_ID = 'security.bc_oauth_client_id';
    public const KEY_OAUTH_CLIENT_SECRET_ENC = 'security.bc_oauth_client_secret_enc';
    public const KEY_OAUTH_TOKEN_ENDPOINT = 'security.bc_oauth_token_endpoint';

    public const AUTH_MODE_BASIC = 'basic';
    public const AUTH_MODE_OAUTH2 = 'oauth2';

    public function __construct(
        private readonly SettingsMetadataGateway $settingsMetadataGateway,
        private readonly SettingsCryptoGatewayInterface $settingsCryptoGateway
    ) {
    }

    public function getAuthMode(): string
    {
        $value = trim((string) ($this->settingsMetadataGateway->getValue(self::KEY_AUTH_MODE) ?? ''));

        return $value === self::AUTH_MODE_OAUTH2 ? self::AUTH_MODE_OAUTH2 : self::AUTH_MODE_BASIC;
    }

    public function setAuthMode(string $mode): bool
    {
        $mode = trim($mode);
        if (!in_array($mode, [self::AUTH_MODE_BASIC, self::AUTH_MODE_OAUTH2], true)) {
            return false;
        }

        return $this->settingsMetadataGateway->set(self::KEY_AUTH_MODE, $mode, self::KEY_AUTH_MODE);
    }

    public function getODataBaseUrl(): string
    {
        $value = trim((string) ($this->settingsMetadataGateway->getValue(self::KEY_ODATA_BASE_URL) ?? ''));

        return $value !== '' ? rtrim($value, '/') : '';
    }

    public function setODataBaseUrl(?string $url): bool
    {
        $url = trim((string) ($url ?? ''));
        if ($url !== '' && !preg_match('#^https://#i', $url)) {
            return false;
        }

        return $this->settingsMetadataGateway->set(
            self::KEY_ODATA_BASE_URL,
            $url !== '' ? rtrim($url, '/') : null,
            self::KEY_ODATA_BASE_URL
        );
    }

    public function getCompanyName(): string
    {
        return trim((string) ($this->settingsMetadataGateway->getValue(self::KEY_COMPANY_NAME) ?? ''));
    }

    public function setCompanyName(?string $name): bool
    {
        $name = trim((string) ($name ?? ''));

        return $this->settingsMetadataGateway->set(
            self::KEY_COMPANY_NAME,
            $name !== '' ? $name : null,
            self::KEY_COMPANY_NAME
        );
    }

    public function getBasicUser(): ?string
    {
        $value = trim((string) ($this->settingsMetadataGateway->getValue(self::KEY_BASIC_USER) ?? ''));

        return $value !== '' ? $value : null;
    }

    public function setBasicUser(?string $user): bool
    {
        $user = trim((string) ($user ?? ''));

        return $this->settingsMetadataGateway->set(
            self::KEY_BASIC_USER,
            $user !== '' ? $user : null,
            self::KEY_BASIC_USER
        );
    }

    public function getBasicPassword(): ?string
    {
        return $this->decryptSetting(self::KEY_BASIC_PASSWORD_ENC);
    }

    public function setBasicPassword(?string $password): bool
    {
        return $this->encryptSetting(self::KEY_BASIC_PASSWORD_ENC, $password);
    }

    public function getOAuthTenantId(): ?string
    {
        $value = trim((string) ($this->settingsMetadataGateway->getValue(self::KEY_OAUTH_TENANT_ID) ?? ''));

        return $value !== '' ? $value : null;
    }

    public function setOAuthTenantId(?string $tenantId): bool
    {
        $tenantId = trim((string) ($tenantId ?? ''));

        return $this->settingsMetadataGateway->set(
            self::KEY_OAUTH_TENANT_ID,
            $tenantId !== '' ? $tenantId : null,
            self::KEY_OAUTH_TENANT_ID
        );
    }

    public function getOAuthClientId(): ?string
    {
        $value = trim((string) ($this->settingsMetadataGateway->getValue(self::KEY_OAUTH_CLIENT_ID) ?? ''));

        return $value !== '' ? $value : null;
    }

    public function setOAuthClientId(?string $clientId): bool
    {
        $clientId = trim((string) ($clientId ?? ''));

        return $this->settingsMetadataGateway->set(
            self::KEY_OAUTH_CLIENT_ID,
            $clientId !== '' ? $clientId : null,
            self::KEY_OAUTH_CLIENT_ID
        );
    }

    public function getOAuthClientSecret(): ?string
    {
        return $this->decryptSetting(self::KEY_OAUTH_CLIENT_SECRET_ENC);
    }

    public function setOAuthClientSecret(?string $secret): bool
    {
        return $this->encryptSetting(self::KEY_OAUTH_CLIENT_SECRET_ENC, $secret);
    }

    public function getOAuthTokenEndpoint(): ?string
    {
        $value = trim((string) ($this->settingsMetadataGateway->getValue(self::KEY_OAUTH_TOKEN_ENDPOINT) ?? ''));

        return $value !== '' ? $value : null;
    }

    public function setOAuthTokenEndpoint(?string $endpoint): bool
    {
        $endpoint = trim((string) ($endpoint ?? ''));
        if ($endpoint !== '' && !preg_match('#^https://#i', $endpoint)) {
            return false;
        }

        return $this->settingsMetadataGateway->set(
            self::KEY_OAUTH_TOKEN_ENDPOINT,
            $endpoint !== '' ? $endpoint : null,
            self::KEY_OAUTH_TOKEN_ENDPOINT
        );
    }

    /**
     * Build the full OData entity URL for a given entity set.
     */
    public function buildEntityUrl(string $entitySet): string
    {
        $baseUrl = $this->getODataBaseUrl();
        $company = rawurlencode($this->getCompanyName());

        return $baseUrl . "/Company('" . $company . "')/" . $entitySet;
    }

    /**
     * Validate that required settings for the current auth mode are present.
     *
     * @return array<int, string> List of missing setting descriptions (empty = valid)
     */
    public function validateConfiguration(): array
    {
        $missing = [];

        if ($this->getODataBaseUrl() === '') {
            $missing[] = 'BC OData Base URL';
        }
        if ($this->getCompanyName() === '') {
            $missing[] = 'BC Company Name';
        }

        if ($this->getAuthMode() === self::AUTH_MODE_BASIC) {
            if ($this->getBasicUser() === null) {
                $missing[] = 'BC Basic Auth User';
            }
            if ($this->getBasicPassword() === null) {
                $missing[] = 'BC Basic Auth Password';
            }
        } else {
            if ($this->getOAuthClientId() === null) {
                $missing[] = 'BC OAuth2 Client ID';
            }
            if ($this->getOAuthClientSecret() === null) {
                $missing[] = 'BC OAuth2 Client Secret';
            }
            if ($this->getOAuthTokenEndpoint() === null) {
                $missing[] = 'BC OAuth2 Token Endpoint';
            }
        }

        return $missing;
    }

    public function isConfigured(): bool
    {
        return $this->validateConfiguration() === [];
    }

    private function decryptSetting(string $key): ?string
    {
        $encrypted = trim((string) ($this->settingsMetadataGateway->getValue($key) ?? ''));
        if ($encrypted === '') {
            return null;
        }

        try {
            $decrypted = trim($this->settingsCryptoGateway->decryptString($encrypted));
        } catch (\Throwable) {
            return null;
        }

        return $decrypted !== '' ? $decrypted : null;
    }

    private function encryptSetting(string $key, ?string $value): bool
    {
        $value = trim((string) ($value ?? ''));
        if ($value === '') {
            return $this->settingsMetadataGateway->set($key, null, $key);
        }

        try {
            $encrypted = $this->settingsCryptoGateway->encryptString($value);
        } catch (\Throwable) {
            return false;
        }

        return $this->settingsMetadataGateway->set($key, $encrypted, $key);
    }
}
