<?php

namespace App\Modules\EmailHosting\Smartermail;

use App\Core\EmailHosting\AbstractEmailHosting;
use Illuminate\Support\Facades\Http;

/**
 * PRC-849 — SmarterMail e-posta hosting modülü (REST API v1).
 *
 * ⚠️ Model eşlemesi (EmailHosting düz-panel ↔ SmarterMail):
 *   - Servis  = SmarterMail **domain**'i (`$params['domain']`).
 *   - Mailbox = domain **user**'ı. Tam CRUD.
 *   - Alias   = domain **alias**'ı (kaynak → hedef(ler)); catch-all destekli.
 *   - Forwarder + Spam: SmarterMail'de son-kullanıcı webmail tercihi → override
 *     edilmez + getClientActions yalnız mailbox+alias gösterir.
 *
 * === Doğrulanmış API sözleşmesi (help.smartertools.com + portal.smartertools.com) ===
 *
 * Auth (sysadmin): `POST {url}/api/v1/auth/authenticate-user`
 *   body {username,password} → {accessToken, refreshToken, accessTokenExpiration}.
 *   Sonraki isteklerde `Authorization: Bearer {accessToken}`.
 *
 * Domain-admin bağlamı (mailbox/alias işlemleri `settings/domain/*` altında domain-admin
 * yetkisi ister): sysadmin token'ıyla domain-admin'i IMPERSONATE et:
 *   `POST /api/v1/settings/domain/impersonate-user` body {email} → domain-scoped accessToken.
 *   (Alternatif: `X-SmarterMailDomain` header'ı ile domain bağlamı belirt.)
 *
 * Kullanıcı ekle/güncelle (domain-admin): `POST /api/v1/settings/domain/user-put`
 *   NESTED body:
 *     userData { userName, fullName, password, isPasswordExpired,
 *                securityFlags { authType, isDomainAdmin },
 *                userMailSettings { maxSize } }
 *
 * Kullanıcı/alias listesi (domain-admin): `POST /api/v1/settings/domain/account-list-search`
 *   body {searchFlags:["users"]} / {searchFlags:["aliases"]}.
 * Alias hedefleri: `POST /api/v1/settings/domain/alias-get` body {name} → {aliasTargetList}.
 *
 * TLS: default sertifika doğrulaması AÇIK; yalnızca `verify_ssl` config'i "0"/false
 * ise kapatılır (self-signed test kurulumları için opt-in).
 *
 * ⚠️ Kapsam dürüstlüğü: sysadmin domain create/enable/delete gövde şemaları birincil
 *   dokümanda tam doğrulanamadı; bu op'lar dokümante verb-tabanlı sysadmin path'lerine
 *   göre yazıldı ancak canlıda teyit gerektirir. user-delete / alias-delete için kesin
 *   REST string birincil kaynakta doğrulanamadığından bu iki op UYDURULMADI: dürüst
 *   "desteklenmiyor" döner (silme webmail/panelden yapılır). Bkz. docs.
 */
class Smartermail extends AbstractEmailHosting
{
    // --- Auth ---
    private const P_AUTH        = '/api/v1/auth/authenticate-user';
    private const P_IMPERSONATE = '/api/v1/settings/domain/impersonate-user';

    // --- Sysadmin (domain lifecycle) — path prefix doğrulandı (GET {domain});
    //     create/enable/delete gövdesi canlıda teyit edilmeli. ---
    private const P_SYS_DOMAIN     = '/api/v1/settings/sysadmin/domain';       // GET /{domain}
    private const P_SYS_DOMAIN_PUT = '/api/v1/settings/sysadmin/domain-put';   // POST create/update

    // --- Domain-admin (mailbox/alias) — doğrulanmış verb-tabanlı path'ler ---
    private const P_ACCOUNT_SEARCH = '/api/v1/settings/domain/account-list-search';
    private const P_USER_PUT        = '/api/v1/settings/domain/user-put';
    private const P_ALIAS_PUT       = '/api/v1/settings/domain/alias-put';
    private const P_ALIAS_GET       = '/api/v1/settings/domain/alias-get';

    /** accessToken memoize (config imzası + domain bazlı). */
    private array $tokenCache = [];

    // ================= Admin config (Product ailesi — canlı surface) =================

    public function adminFields()
    {
        return [
            'server_url' => [
                'name' => 'SmarterMail URL',
                'type' => 'text',
                'description' => 'Örn: https://mail.example.com (web arayüzü kökü).',
                'required' => true,
            ],
            'admin_user' => [
                'name' => 'System Admin Kullanıcı',
                'type' => 'text',
                'description' => 'SmarterMail system admin kullanıcı adı.',
                'required' => true,
            ],
            'admin_password' => [
                'name' => 'System Admin Şifre',
                'type' => 'password',
                'description' => 'System admin şifresi.',
                'required' => true,
            ],
            'domain_admin_email' => [
                'name' => 'Domain Admin E-posta (opsiyonel)',
                'type' => 'text',
                'description' => 'Mailbox/alias işlemleri için impersonate edilecek domain-admin '
                    . 'hesabı (örn admin@example.com). Boşsa varsayılan olarak admin@{domain} denenir.',
                'required' => false,
            ],
            'verify_ssl' => [
                'name' => 'TLS Sertifikası Doğrula',
                'type' => 'select',
                'options' => ['1' => 'Evet (önerilir)', '0' => 'Hayır (yalnız test)'],
                'default' => '1',
                'description' => 'Kapatmak self-signed sertifikalı test kurulumları içindir; '
                    . 'production\'da açık bırakın.',
                'required' => false,
            ],
        ];
    }

    /** SmarterMail'de forwarder/spam per-user → yalnız mailbox+alias paneli. */
    public function getClientActions(): array
    {
        $actions = parent::getClientActions();
        unset($actions['forwarders'], $actions['spam']);

        return $actions;
    }

    // ================= Lifecycle (servis = domain) =================
    // ⚠️ sysadmin domain create/enable/delete gövde şemaları canlıda teyit edilmeli.

    public function createAccount(array $params)
    {
        $domain = $this->domainOf($params);
        if ($domain === '') {
            return $this->fail('Domain tanımlı değil.');
        }
        try {
            $this->sysApi($params, 'POST', self::P_SYS_DOMAIN_PUT, [
                'domainData' => [
                    'name' => $domain,
                    'domainName' => $domain,
                    'isEnabled' => true,
                ],
            ]);

            return $this->ok('Domain oluşturuldu: ' . $domain);
        } catch (\Throwable $e) {
            return $this->fail('Domain oluşturulamadı: ' . $e->getMessage());
        }
    }

    public function suspendAccount(array $params)
    {
        return $this->setDomainEnabled($params, false, 'Domain askıya alındı.');
    }

    public function unsuspendAccount(array $params)
    {
        return $this->setDomainEnabled($params, true, 'Domain devam ettirildi.');
    }

    public function terminateAccount(array $params)
    {
        $domain = $this->domainOf($params);
        if ($domain === '') {
            return $this->fail('Domain tanımlı değil.');
        }
        try {
            $this->sysApi($params, 'DELETE', self::P_SYS_DOMAIN . '/' . rawurlencode($domain));

            return $this->ok('Domain silindi: ' . $domain);
        } catch (\Throwable $e) {
            return $this->fail('Domain silinemedi: ' . $e->getMessage());
        }
    }

    public function changeAccountPassword(array $params)
    {
        return $this->ok('SmarterMail domain seviyesinde şifre yoktur (mailbox şifresi mailbox panelinden).');
    }

    public function testConnection(array $params)
    {
        try {
            $this->sysToken($params);

            return $this->ok('SmarterMail admin bağlantısı başarılı.');
        } catch (\Throwable $e) {
            return $this->fail('Bağlantı başarısız: ' . $e->getMessage());
        }
    }

    public function paramSchema(): array
    {
        return [];
    }

    // ================= Mailbox (= domain user, domain-admin bağlamı) =================

    public function listMailboxes(array $params): array
    {
        try {
            $data = $this->domainApi($params, 'POST', self::P_ACCOUNT_SEARCH, [
                'searchFlags' => ['users'],
            ]);
            $domain = $this->domainOf($params);
            $mailboxes = [];
            foreach (($data['users'] ?? $data['accounts'] ?? $data['data'] ?? []) as $u) {
                $email = $u['userName'] ?? $u['emailAddress'] ?? $u['username'] ?? '';
                if ($email === '') {
                    continue;
                }
                if (! str_contains($email, '@') && $domain !== '') {
                    $email .= '@' . $domain;
                }
                $quotaBytes = (int) ($u['maxSize']
                    ?? $u['maxMailboxSize']
                    ?? ($u['userMailSettings']['maxSize'] ?? 0));
                $mailboxes[] = [
                    'email' => $email,
                    'quota_mb' => (int) round($quotaBytes / 1048576),
                ];
            }

            return $this->ok('', ['mailboxes' => $mailboxes]);
        } catch (\Throwable $e) {
            return $this->fail('Posta kutuları alınamadı: ' . $e->getMessage());
        }
    }

    public function createMailbox(array $params): array
    {
        $email = trim((string) ($params['email'] ?? ''));
        $password = (string) ($params['password'] ?? '');
        if ($email === '' || $password === '') {
            return $this->fail('E-posta ve şifre gerekli.');
        }
        try {
            $this->domainApi($params, 'POST', self::P_USER_PUT, [
                'userData' => [
                    'userName' => $this->localPart($email, $params),
                    'password' => $password,
                    'isPasswordExpired' => false,
                    'securityFlags' => [
                        'authType' => 0,      // 0 = SmarterMail (yerel) kimlik doğrulama
                        'isDomainAdmin' => false,
                    ],
                    'userMailSettings' => [
                        'maxSize' => ((int) ($params['quota_mb'] ?? 0)) * 1048576,
                    ],
                ],
            ]);

            return $this->listMailboxes($params);
        } catch (\Throwable $e) {
            return $this->fail('Posta kutusu oluşturulamadı: ' . $e->getMessage());
        }
    }

    /**
     * ⚠️ Kullanıcı silmenin kesin REST endpoint'i birincil dokümanda doğrulanamadı;
     * UYDURMA yerine dürüst "desteklenmiyor" döner (silme SmarterMail panelinden).
     */
    public function deleteMailbox(array $params): array
    {
        return $this->fail(
            'Posta kutusu silme SmarterMail REST v1\'de bu modülde desteklenmiyor '
            . '(doğrulanmış endpoint yok) — SmarterMail yönetim panelinden silin.'
        );
    }

    public function setMailboxPassword(array $params): array
    {
        $password = (string) ($params['password'] ?? '');
        $email = trim((string) ($params['email'] ?? ''));
        if ($password === '' || $email === '') {
            return $this->fail('E-posta ve şifre gerekli.');
        }
        try {
            // user-put upsert: userName ile hedef kullanıcıyı güncelle.
            $this->domainApi($params, 'POST', self::P_USER_PUT, [
                'userData' => [
                    'userName' => $this->localPart($email, $params),
                    'password' => $password,
                    'isPasswordExpired' => false,
                ],
            ]);

            return $this->ok('Şifre güncellendi.');
        } catch (\Throwable $e) {
            return $this->fail('Şifre güncellenemedi: ' . $e->getMessage());
        }
    }

    public function setMailboxQuota(array $params): array
    {
        $email = trim((string) ($params['email'] ?? ''));
        if ($email === '') {
            return $this->fail('E-posta gerekli.');
        }
        try {
            $this->domainApi($params, 'POST', self::P_USER_PUT, [
                'userData' => [
                    'userName' => $this->localPart($email, $params),
                    'userMailSettings' => [
                        'maxSize' => ((int) ($params['quota_mb'] ?? 0)) * 1048576,
                    ],
                ],
            ]);

            return $this->listMailboxes($params);
        } catch (\Throwable $e) {
            return $this->fail('Kota güncellenemedi: ' . $e->getMessage());
        }
    }

    // ================= Alias (= domain alias, domain-admin bağlamı) =================

    public function listAliases(array $params): array
    {
        $domain = $this->domainOf($params);
        try {
            $data = $this->domainApi($params, 'POST', self::P_ACCOUNT_SEARCH, [
                'searchFlags' => ['aliases'],
            ]);
            $aliases = [];
            foreach (($data['aliases'] ?? $data['accounts'] ?? $data['data'] ?? []) as $a) {
                $source = $a['name'] ?? $a['aliasName'] ?? '';
                if ($source === '') {
                    continue;
                }
                $dests = $a['aliasTargetList'] ?? $a['targetList'] ?? $a['destinations'] ?? $a['targets'] ?? null;
                // Liste araması hedefleri döndürmeyebilir → alias-get ile detaya in.
                if ($dests === null || $dests === []) {
                    $dests = $this->aliasTargets($params, $source);
                }
                $aliases[] = [
                    'source' => (is_string($source) && ! str_contains($source, '@') && $domain !== '')
                        ? $source . '@' . $domain
                        : $source,
                    'destination' => is_array($dests) ? implode(', ', $dests) : (string) $dests,
                ];
            }

            return $this->ok('', ['aliases' => $aliases]);
        } catch (\Throwable $e) {
            return $this->fail('Takma adlar alınamadı: ' . $e->getMessage());
        }
    }

    public function createAlias(array $params): array
    {
        $source = trim((string) ($params['source'] ?? ''));
        $destination = trim((string) ($params['destination'] ?? ''));
        if ($source === '' || $destination === '') {
            return $this->fail('Kaynak ve hedef gerekli.');
        }

        // EmailHostingInterface sözleşmesi: destination='*' → catch-all.
        // SmarterMail catch-all'u domain seviyesindedir; alias adını catch-all
        // (boş/yerel-kısım-yok) olarak set ederiz.
        $isCatchAll = ($destination === '*');
        $targets = $isCatchAll
            ? [$this->domainOf($params)]      // catch-all: gelen tüm postayı domaine düşür
            : array_values(array_filter(array_map('trim', explode(',', $destination))));

        try {
            $this->domainApi($params, 'POST', self::P_ALIAS_PUT, [
                'aliasData' => [
                    'name' => $this->localPart($source, $params),
                    'isCatchAll' => $isCatchAll,
                    'aliasTargetList' => $targets,
                ],
            ]);

            return $this->listAliases($params);
        } catch (\Throwable $e) {
            return $this->fail('Takma ad oluşturulamadı: ' . $e->getMessage());
        }
    }

    /**
     * ⚠️ Alias silmenin kesin REST endpoint'i birincil dokümanda doğrulanamadı;
     * UYDURMA yerine dürüst "desteklenmiyor" döner (silme SmarterMail panelinden).
     */
    public function deleteAlias(array $params): array
    {
        return $this->fail(
            'Takma ad silme SmarterMail REST v1\'de bu modülde desteklenmiyor '
            . '(doğrulanmış endpoint yok) — SmarterMail yönetim panelinden silin.'
        );
    }

    // ================= REST altyapısı =================

    private function cfg(array $params, string $key, string $default = ''): string
    {
        return (string) ($params['module_config'][$key] ?? $this->config[$key] ?? $default);
    }

    private function domainOf(array $params): string
    {
        return trim((string) ($params['domain'] ?? $this->cfg($params, 'domain')));
    }

    private function baseUrl(array $params): string
    {
        return rtrim($this->cfg($params, 'server_url'), '/');
    }

    /** TLS doğrulaması varsayılan AÇIK; yalnız verify_ssl="0"/false ise kapalı. */
    private function verifySsl(array $params): bool
    {
        $v = strtolower(trim($this->cfg($params, 'verify_ssl', '1')));

        return ! in_array($v, ['0', 'false', 'no', 'off'], true);
    }

    /** Impersonate edilecek domain-admin e-postası. */
    private function domainAdminEmail(array $params): string
    {
        $email = trim($this->cfg($params, 'domain_admin_email'));
        if ($email !== '') {
            return $email;
        }
        $domain = $this->domainOf($params);

        return $domain !== '' ? 'admin@' . $domain : '';
    }

    /** E-postanın yerel kısmını çıkarır (SmarterMail userData.userName = local-part). */
    private function localPart(string $email, array $params): string
    {
        $email = trim($email);
        if (str_contains($email, '@')) {
            return explode('@', $email, 2)[0];
        }

        return $email;
    }

    /** Ortak HTTP client (TLS toggle + timeout + JSON). */
    private function client(array $params)
    {
        $http = Http::timeout(30)->acceptJson();
        if (! $this->verifySsl($params)) {
            $http = $http->withoutVerifying();
        }

        return $http;
    }

    /** sysadmin authenticate-user → accessToken (memoize). */
    private function sysToken(array $params): string
    {
        $sig = 'sys|' . md5($this->baseUrl($params) . '|' . $this->cfg($params, 'admin_user'));
        if (isset($this->tokenCache[$sig])) {
            return $this->tokenCache[$sig];
        }
        $resp = $this->client($params)->post($this->baseUrl($params) . self::P_AUTH, [
            'username' => $this->cfg($params, 'admin_user'),
            'password' => $this->cfg($params, 'admin_password'),
        ]);
        $json = $resp->json() ?? [];
        $token = $json['accessToken'] ?? $json['access_token'] ?? '';
        if (! $resp->successful() || $token === '') {
            throw new \RuntimeException(
                'accessToken alınamadı (kimlik/endpoint hatalı olabilir, HTTP ' . $resp->status() . ').'
            );
        }

        return $this->tokenCache[$sig] = $token;
    }

    /**
     * Domain-admin bağlamı için token: sysadmin token'ıyla domain-admin'i impersonate et.
     * Impersonation başarısız olursa sysadmin token'ına + X-SmarterMailDomain header'ına
     * geri düşülür (domainApi header'ı ekler).
     */
    private function domainToken(array $params): string
    {
        $domain = $this->domainOf($params);
        $sig = 'dom|' . md5($this->baseUrl($params) . '|' . $domain);
        if (isset($this->tokenCache[$sig])) {
            return $this->tokenCache[$sig];
        }

        $sysToken = $this->sysToken($params);
        $adminEmail = $this->domainAdminEmail($params);
        if ($adminEmail === '') {
            return $this->tokenCache[$sig] = $sysToken;
        }

        try {
            $resp = $this->client($params)
                ->withToken($sysToken)
                ->post($this->baseUrl($params) . self::P_IMPERSONATE, ['email' => $adminEmail]);
            $json = $resp->json() ?? [];
            $token = $json['accessToken'] ?? $json['impersonateAccessToken'] ?? '';
            if ($resp->successful() && $token !== '') {
                return $this->tokenCache[$sig] = $token;
            }
        } catch (\Throwable $e) {
            // impersonation yoksa header-fallback (aşağıda) devreye girer.
        }

        // Fallback: sysadmin token + X-SmarterMailDomain header ile domain bağlamı.
        return $this->tokenCache[$sig] = $sysToken;
    }

    /** sysadmin bağlamında Bearer'lı JSON isteği. */
    private function sysApi(array $params, string $method, string $path, array $body = []): array
    {
        return $this->request($params, $this->sysToken($params), $method, $path, $body);
    }

    /** domain-admin bağlamında Bearer'lı JSON isteği (+ X-SmarterMailDomain header). */
    private function domainApi(array $params, string $method, string $path, array $body = []): array
    {
        $req = $this->client($params)
            ->withToken($this->domainToken($params))
            ->withHeaders(['X-SmarterMailDomain' => $this->domainOf($params)]);

        return $this->send($params, $req, $method, $path, $body);
    }

    private function request(array $params, string $token, string $method, string $path, array $body): array
    {
        $req = $this->client($params)->withToken($token);

        return $this->send($params, $req, $method, $path, $body);
    }

    /**
     * İsteği gönder + false-success koru: HTTP 200 dönse bile gövdedeki
     * success=false / resultCode hata bayrağını yakala.
     */
    private function send(array $params, $req, string $method, string $path, array $body): array
    {
        $resp = $req->send($method, $this->baseUrl($params) . $path, $body ? ['json' => $body] : []);

        $json = $resp->json() ?? [];
        $bodyOk = true;
        if (is_array($json)) {
            if (($json['success'] ?? null) === false) {
                $bodyOk = false;
            }
            if (isset($json['resultCode']) && (int) $json['resultCode'] !== 0 && ($json['success'] ?? null) !== true) {
                $bodyOk = false;
            }
        }

        if (! $resp->successful() || ! $bodyOk) {
            $msg = (is_array($json) ? ($json['message'] ?? $json['messageDetail'] ?? null) : null)
                ?? ('HTTP ' . $resp->status());
            throw new \RuntimeException('SmarterMail hatası: ' . $msg);
        }

        return is_array($json) ? $json : [];
    }

    /** Alias hedeflerini alias-get ile çöz (account-list-search hedef döndürmezse). */
    private function aliasTargets(array $params, string $name): array
    {
        try {
            $data = $this->domainApi($params, 'POST', self::P_ALIAS_GET, [
                'name' => (str_contains($name, '@') ? explode('@', $name, 2)[0] : $name),
            ]);
            $targets = $data['aliasTargetList']
                ?? $data['aliasData']['aliasTargetList']
                ?? $data['targetList']
                ?? [];

            return is_array($targets) ? $targets : [];
        } catch (\Throwable $e) {
            return [];
        }
    }

    private function setDomainEnabled(array $params, bool $enabled, string $okMsg): array
    {
        $domain = $this->domainOf($params);
        if ($domain === '') {
            return $this->fail('Domain tanımlı değil.');
        }
        try {
            $this->sysApi($params, 'POST', self::P_SYS_DOMAIN_PUT, [
                'domainData' => [
                    'name' => $domain,
                    'domainName' => $domain,
                    'isEnabled' => $enabled,
                ],
            ]);

            return $this->ok($okMsg);
        } catch (\Throwable $e) {
            return $this->fail('Durum güncellenemedi: ' . $e->getMessage());
        }
    }
}
