<?php

namespace MintyPHP\Service\Auth;

use MintyPHP\Repository\Tenant\TenantMicrosoftAuthRepositoryInterface;

class TenantSsoService
{
    public const PROVIDER_MICROSOFT = 'microsoft';

    private const PROFILE_SYNC_FIELD_OPTIONS = [
        'first_name' => 'Sync first name',
        'last_name' => 'Sync last name',
        'phone' => 'Sync phone',
        'mobile' => 'Sync mobile',
        'avatar' => 'Sync avatar image',
    ];

    private const DEFAULT_PROFILE_SYNC_FIELDS = ['first_name', 'last_name'];

    public function __construct(
        private readonly AuthTenantGateway $tenantGateway,
        private readonly TenantMicrosoftAuthRepositoryInterface $tenantMicrosoftAuthRepository,
        private readonly AuthSettingsGateway $settingsGateway,
        private readonly AuthCryptoGateway $cryptoGateway
    ) {
    }

    public function microsoftProviderKey(): string
    {
        return self::PROVIDER_MICROSOFT;
    }

    public function findTenantByLoginSlug(string $slug): ?array
    {
        $slug = trim(strtolower($slug));
        if ($slug === '') {
            return null;
        }

        if (preg_match('/^[a-f0-9-]{36}$/', $slug)) {
            $tenant = $this->tenantGateway->findByUuid($slug);
            if ($tenant && (string) ($tenant['status'] ?? 'active') === 'active') {
                return $tenant;
            }
        }

        $matches = [];
        foreach ($this->tenantGateway->list() as $tenant) {
            if ((string) ($tenant['status'] ?? 'active') !== 'active') {
                continue;
            }
            if ($this->slugify((string) ($tenant['description'] ?? '')) === $slug) {
                $matches[] = $tenant;
            }
        }

        // Reject ambiguous slugs — if two tenants have the same name-slug, neither matches.
        if (count($matches) !== 1) {
            return null;
        }

        return $matches[0];
    }

    public function tenantLoginSlug(array $tenant): string
    {
        $slug = $this->slugify((string) ($tenant['description'] ?? ''));
        if ($slug !== '') {
            return $slug;
        }

        return strtolower((string) ($tenant['uuid'] ?? ''));
    }

    public function getTenantMicrosoftAuth(int $tenantId): array
    {
        $row = $this->tenantMicrosoftAuthRepository->findByTenantId($tenantId) ?? [];
        $syncProfileOnLogin = !empty($row['sync_profile_on_login']);
        $syncFields = $this->normalizeProfileSyncFields($row['sync_profile_fields'] ?? []);
        if ($syncProfileOnLogin && !$syncFields) {
            $syncFields = $this->defaultProfileSyncFields();
        }

        return [
            'enabled' => !empty($row['enabled']),
            'enforce_microsoft_login' => !empty($row['enforce_microsoft_login']),
            'sync_profile_on_login' => $syncProfileOnLogin,
            'sync_profile_fields' => $this->profileSyncFieldsCsv($syncFields),
            'sync_profile_fields_list' => $syncFields,
            'entra_tenant_id' => strtolower(trim((string) ($row['entra_tenant_id'] ?? ''))),
            'allowed_domains' => $this->normalizeDomains((string) ($row['allowed_domains'] ?? '')),
            'use_shared_app' => !array_key_exists('use_shared_app', $row) || !empty($row['use_shared_app']),
            'client_id_override' => trim((string) ($row['client_id_override'] ?? '')),
            'client_secret_override_enc' => trim((string) ($row['client_secret_override_enc'] ?? '')),
            'auto_remember_mode' => array_key_exists('auto_remember_mode', $row) && $row['auto_remember_mode'] !== null
                ? (int) $row['auto_remember_mode']
                : null,
        ];
    }

    public function buildMicrosoftUiState(int $tenantId, array $input = []): array
    {
        $existing = $this->getTenantMicrosoftAuth($tenantId);
        $state = $this->normalizeMicrosoftInputState($input, $existing);
        $configState = $this->evaluateMicrosoftConfigState($state);

        return [
            'enabled' => $state['enabled'],
            'config_complete' => !$state['enabled'] || $configState['complete'],
            'config_error_code' => (string) ($configState['error_code'] ?? ''),
            'config_error_label' => $this->microsoftConfigErrorLabel((string) ($configState['error_code'] ?? '')),
            'password_mode' => !$state['enabled']
                ? 'local_only'
                : ($state['enforce_microsoft_login'] ? 'microsoft_only' : 'local_and_microsoft'),
            'credential_source' => $state['use_shared_app'] ? 'shared' : 'override',
            'sync_needs_graph' => $state['sync_profile_on_login']
                && $this->profileSyncFieldsRequireGraph($state['sync_profile_fields']),
        ];
    }

    public function isLocalPasswordLoginAllowed(int $tenantId): bool
    {
        $auth = $this->getTenantMicrosoftAuth($tenantId);
        if (!$auth['enabled']) {
            return true;
        }

        return !$auth['enforce_microsoft_login'];
    }

    public function resolveTenantLoginMethods(int $tenantId): array
    {
        $tenant = $this->tenantGateway->findById($tenantId);
        if (!$tenant || (string) ($tenant['status'] ?? 'active') !== 'active') {
            return [
                'local' => false,
                'microsoft' => false,
                'microsoft_reason' => 'tenant_not_found',
            ];
        }

        $configResult = $this->getEffectiveMicrosoftProviderConfig($tenant);

        return [
            'local' => $this->isLocalPasswordLoginAllowed($tenantId),
            'microsoft' => !empty($configResult['ok']),
            'microsoft_reason' => $configResult['ok'] ?? false
                ? ''
                : (string) ($configResult['error'] ?? 'sso_disabled'),
        ];
    }

    public function getEffectiveMicrosoftProviderConfig(array $tenant): array
    {
        $tenantId = (int) ($tenant['id'] ?? 0);
        if ($tenantId <= 0) {
            return ['ok' => false, 'error' => 'tenant_not_found'];
        }

        $auth = $this->getTenantMicrosoftAuth($tenantId);
        if (!$auth['enabled']) {
            return ['ok' => false, 'error' => 'sso_disabled'];
        }

        $entraTenantId = $auth['entra_tenant_id'];
        if (!preg_match('/^[a-f0-9-]{36}$/', $entraTenantId)) {
            return ['ok' => false, 'error' => 'tenant_id_invalid'];
        }

        $useSharedApp = $auth['use_shared_app'];
        $clientId = '';
        $clientSecret = '';

        if ($useSharedApp) {
            $clientId = $this->settingsGateway->getMicrosoftSharedClientId();
            $clientSecret = $this->settingsGateway->getMicrosoftSharedClientSecret();
            if (trim($clientId) === '' || trim($clientSecret) === '') {
                return ['ok' => false, 'error' => 'shared_credentials_missing'];
            }
        } else {
            $clientId = (string) $auth['client_id_override'];
            if (trim($clientId) === '') {
                return ['ok' => false, 'error' => 'client_id_override_required'];
            }
            if ($auth['client_secret_override_enc'] === '') {
                return ['ok' => false, 'error' => 'client_secret_override_required'];
            }
            if ($auth['client_secret_override_enc'] !== '') {
                try {
                    $clientSecret = $this->cryptoGateway->decryptString($auth['client_secret_override_enc']);
                } catch (\Throwable $exception) {
                    return ['ok' => false, 'error' => 'secret_invalid'];
                }
            }
            if (trim($clientSecret) === '') {
                return ['ok' => false, 'error' => 'client_secret_override_required'];
            }
        }

        $clientId = trim($clientId);
        $clientSecret = trim($clientSecret);

        return [
            'ok' => true,
            'config' => [
                'provider' => self::PROVIDER_MICROSOFT,
                'tenant_id' => $entraTenantId,
                'client_id' => $clientId,
                'client_secret' => $clientSecret,
                'allowed_domains' => $auth['allowed_domains'],
                'enforce_microsoft_login' => $auth['enforce_microsoft_login'],
                'sync_profile_on_login' => $auth['sync_profile_on_login'],
                'sync_profile_fields' => $auth['sync_profile_fields_list'],
                'sync_profile_needs_graph' => $auth['sync_profile_on_login']
                    && $this->profileSyncFieldsRequireGraph($auth['sync_profile_fields_list']),
                'authority' => $this->settingsGateway->getMicrosoftAuthority(),
                'use_shared_app' => $useSharedApp,
            ],
        ];
    }

    public function saveTenantMicrosoftAuth(int $tenantId, array $input): array
    {
        $tenant = $this->tenantGateway->findById($tenantId);
        if (!$tenant) {
            return ['ok' => false, 'errors' => [t('Tenant not found')]];
        }

        $enabled = !empty($input['microsoft_enabled']);
        $enforce = !empty($input['enforce_microsoft_login']);
        $entraTenantId = strtolower(trim((string) ($input['entra_tenant_id'] ?? '')));
        $allowedDomains = $this->normalizeDomains((string) ($input['allowed_domains'] ?? ''));
        $syncProfileOnLogin = !empty($input['sync_profile_on_login']);
        $syncProfileFields = $this->normalizeProfileSyncFields($input['sync_profile_fields'] ?? []);
        if ($syncProfileOnLogin && !$syncProfileFields) {
            $syncProfileFields = $this->defaultProfileSyncFields();
        }
        $syncProfileFieldsCsv = $this->profileSyncFieldsCsv($syncProfileFields);
        $useSharedApp = !empty($input['use_shared_app']);
        $clientIdOverride = trim((string) ($input['client_id_override'] ?? ''));
        $clientSecretOverride = trim((string) ($input['client_secret_override'] ?? ''));
        $clearSecret = !empty($input['clear_client_secret_override']);

        $errors = [];

        $existing = $this->getTenantMicrosoftAuth($tenantId);
        $clientSecretOverrideEnc = (string) ($existing['client_secret_override_enc'] ?? '');
        if ($clearSecret) {
            $clientSecretOverrideEnc = '';
        } elseif ($clientSecretOverride !== '') {
            if (!$this->cryptoGateway->isConfigured()) {
                $errors[] = t('APP_CRYPTO_KEY is missing or invalid');
            } else {
                try {
                    $clientSecretOverrideEnc = $this->cryptoGateway->encryptString($clientSecretOverride);
                } catch (\Throwable $exception) {
                    $errors[] = t('Client secret could not be encrypted');
                }
            }
        }

        $stateForValidation = [
            'enabled' => $enabled,
            'enforce_microsoft_login' => $enforce,
            'sync_profile_on_login' => $syncProfileOnLogin,
            'sync_profile_fields' => $syncProfileFields,
            'entra_tenant_id' => $entraTenantId,
            'use_shared_app' => $useSharedApp,
            'client_id_override' => $clientIdOverride,
            'client_secret_override_enc' => $clientSecretOverrideEnc,
        ];
        $configState = $this->evaluateMicrosoftConfigState($stateForValidation);
        if ($enabled && !($configState['complete'] ?? false)) {
            $label = $this->microsoftConfigErrorLabel((string) ($configState['error_code'] ?? ''));
            if ($label !== '') {
                $errors[] = $label;
            }
        }
        if ($enabled && $enforce && !($configState['complete'] ?? false)) {
            $errors[] = t('Microsoft-only login requires a complete configuration');
        }

        if ($errors) {
            return ['ok' => false, 'errors' => $errors];
        }

        $autoRememberMode = $this->normalizeAutoRememberMode($input['microsoft_auto_remember_mode'] ?? null);

        $saved = $this->tenantMicrosoftAuthRepository->upsertByTenantId($tenantId, [
            'enabled' => $enabled,
            'enforce_microsoft_login' => $enabled ? $enforce : false,
            'sync_profile_on_login' => $enabled ? $syncProfileOnLogin : false,
            'sync_profile_fields' => $syncProfileFieldsCsv !== '' ? $syncProfileFieldsCsv : null,
            'entra_tenant_id' => $enabled ? $entraTenantId : null,
            'allowed_domains' => $allowedDomains === '' ? null : $allowedDomains,
            'use_shared_app' => $useSharedApp,
            'client_id_override' => $useSharedApp ? null : ($clientIdOverride !== '' ? $clientIdOverride : null),
            'client_secret_override_enc' => $useSharedApp ? null : ($clientSecretOverrideEnc !== '' ? $clientSecretOverrideEnc : null),
            'auto_remember_mode' => $autoRememberMode,
        ]);

        if (!$saved) {
            return ['ok' => false, 'errors' => [t('Tenant SSO settings could not be saved')]];
        }

        return ['ok' => true];
    }

    // Empty allowedDomains list means all email domains are permitted.
    public function isEmailDomainAllowed(string $email, array $allowedDomains): bool
    {
        $allowedDomains = array_values(array_filter(array_map(
            static fn ($domain): string => strtolower(trim((string) $domain)),
            $allowedDomains
        ), static fn (string $domain): bool => $domain !== ''));

        if (!$allowedDomains) {
            return true;
        }

        $parts = explode('@', strtolower(trim($email)));
        if (count($parts) !== 2) {
            return false;
        }

        return in_array($parts[1], $allowedDomains, true);
    }

    public function profileSyncFieldOptions(): array
    {
        return self::PROFILE_SYNC_FIELD_OPTIONS;
    }

    public function defaultProfileSyncFields(): array
    {
        return self::DEFAULT_PROFILE_SYNC_FIELDS;
    }

    public function normalizeProfileSyncFields($raw): array
    {
        if (is_string($raw)) {
            $raw = preg_split('/[\s,;]+/', $raw) ?: [];
        } elseif (!is_array($raw)) {
            return [];
        }

        $allowed = array_fill_keys(array_keys(self::PROFILE_SYNC_FIELD_OPTIONS), true);
        $normalized = [];
        foreach ($raw as $item) {
            $key = strtolower(trim((string) $item));
            if ($key === '' || !isset($allowed[$key])) {
                continue;
            }
            $normalized[$key] = true;
        }

        return array_keys($normalized);
    }

    public function profileSyncFieldsCsv(array $fields): string
    {
        $fields = $this->normalizeProfileSyncFields($fields);
        return implode(',', $fields);
    }

    public function profileSyncFieldsRequireGraph(array $fields): bool
    {
        $fields = $this->normalizeProfileSyncFields($fields);
        return in_array('phone', $fields, true)
            || in_array('mobile', $fields, true)
            || in_array('avatar', $fields, true);
    }

    public function shouldAutoRememberOnMicrosoftLogin(int $tenantId): bool
    {
        $auth = $this->getTenantMicrosoftAuth($tenantId);
        $mode = $auth['auto_remember_mode'] ?? null;
        if ($mode === 1) {
            return true;
        }
        if ($mode === 0) {
            return false;
        }

        return $this->settingsGateway->isMicrosoftAutoRememberDefault();
    }

    private function normalizeAutoRememberMode(mixed $value): ?int
    {
        if ($value === null || $value === '' || $value === 'inherit') {
            return null;
        }
        $intVal = (int) $value;
        return in_array($intVal, [0, 1], true) ? $intVal : null;
    }

    private function normalizeDomains(string $raw): string
    {
        $parts = preg_split('/[,\n;\r]+/', $raw) ?: [];
        $domains = [];
        foreach ($parts as $part) {
            $domain = strtolower(trim($part));
            $domain = preg_replace('/^@+/', '', $domain);
            if ($domain === '') {
                continue;
            }
            if (!preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/i', $domain)) {
                continue;
            }
            $domains[] = $domain;
        }
        $domains = array_values(array_unique($domains));
        return implode(',', $domains);
    }

    private function normalizeMicrosoftInputState(array $input, array $existing): array
    {
        $enabled = array_key_exists('microsoft_enabled', $input)
            ? !empty($input['microsoft_enabled'])
            : !empty($existing['enabled']);
        $enforce = array_key_exists('enforce_microsoft_login', $input)
            ? !empty($input['enforce_microsoft_login'])
            : !empty($existing['enforce_microsoft_login']);
        $syncProfileOnLogin = array_key_exists('sync_profile_on_login', $input)
            ? !empty($input['sync_profile_on_login'])
            : !empty($existing['sync_profile_on_login']);
        $syncProfileFields = array_key_exists('sync_profile_fields', $input)
            ? $this->normalizeProfileSyncFields($input['sync_profile_fields'] ?? [])
            : $this->normalizeProfileSyncFields($existing['sync_profile_fields_list'] ?? []);
        if ($syncProfileOnLogin && !$syncProfileFields) {
            $syncProfileFields = $this->defaultProfileSyncFields();
        }

        $entraTenantId = array_key_exists('entra_tenant_id', $input)
            ? strtolower(trim((string) ($input['entra_tenant_id'] ?? '')))
            : strtolower(trim((string) ($existing['entra_tenant_id'] ?? '')));
        $useSharedApp = array_key_exists('use_shared_app', $input)
            ? !empty($input['use_shared_app'])
            : !empty($existing['use_shared_app']);
        $clientIdOverride = array_key_exists('client_id_override', $input)
            ? trim((string) ($input['client_id_override'] ?? ''))
            : trim((string) ($existing['client_id_override'] ?? ''));

        $clientSecretOverrideEnc = trim((string) ($existing['client_secret_override_enc'] ?? ''));
        if (array_key_exists('clear_client_secret_override', $input) && !empty($input['clear_client_secret_override'])) {
            $clientSecretOverrideEnc = '';
        } elseif (trim((string) ($input['client_secret_override'] ?? '')) !== '') {
            // For UI status, a newly typed secret is considered present before persisting.
            $clientSecretOverrideEnc = '__pending_secret__';
        }

        return [
            'enabled' => $enabled,
            'enforce_microsoft_login' => $enforce,
            'sync_profile_on_login' => $syncProfileOnLogin,
            'sync_profile_fields' => $syncProfileFields,
            'entra_tenant_id' => $entraTenantId,
            'use_shared_app' => $useSharedApp,
            'client_id_override' => $clientIdOverride,
            'client_secret_override_enc' => $clientSecretOverrideEnc,
        ];
    }

    private function evaluateMicrosoftConfigState(array $state): array
    {
        if (empty($state['enabled'])) {
            return ['complete' => true, 'error_code' => ''];
        }

        $entraTenantId = strtolower(trim((string) ($state['entra_tenant_id'] ?? '')));
        if (!preg_match('/^[a-f0-9-]{36}$/', $entraTenantId)) {
            return ['complete' => false, 'error_code' => 'tenant_id_invalid'];
        }

        if (!empty($state['use_shared_app'])) {
            $sharedClientId = trim($this->settingsGateway->getMicrosoftSharedClientId());
            $sharedClientSecret = trim($this->settingsGateway->getMicrosoftSharedClientSecret());
            if ($sharedClientId === '' || $sharedClientSecret === '') {
                return ['complete' => false, 'error_code' => 'shared_credentials_missing'];
            }
            return ['complete' => true, 'error_code' => ''];
        }

        $clientIdOverride = trim((string) ($state['client_id_override'] ?? ''));
        if ($clientIdOverride === '') {
            return ['complete' => false, 'error_code' => 'client_id_override_required'];
        }

        $secretEnc = trim((string) ($state['client_secret_override_enc'] ?? ''));
        if ($secretEnc === '') {
            return ['complete' => false, 'error_code' => 'client_secret_override_required'];
        }
        if ($secretEnc !== '__pending_secret__') {
            try {
                $secret = trim($this->cryptoGateway->decryptString($secretEnc));
            } catch (\Throwable $exception) {
                return ['complete' => false, 'error_code' => 'secret_invalid'];
            }
            if ($secret === '') {
                return ['complete' => false, 'error_code' => 'client_secret_override_required'];
            }
        }

        return ['complete' => true, 'error_code' => ''];
    }

    private function microsoftConfigErrorLabel(string $errorCode): string
    {
        return match ($errorCode) {
            'tenant_id_invalid' => t('Entra tenant ID is invalid'),
            'shared_credentials_missing' => t('Shared credentials missing'),
            'client_id_override_required' => t('Client ID override is required'),
            'client_secret_override_required' => t('Client secret override is required'),
            'secret_invalid' => t('Client secret override is invalid'),
            'client_credentials_missing' => t('Client credentials are missing'),
            default => '',
        };
    }

    private function slugify(string $value): string
    {
        $value = strtolower(trim($value));
        if ($value === '') {
            return '';
        }
        $value = preg_replace('/[^a-z0-9]+/', '-', $value) ?? '';
        return trim($value, '-');
    }
}
