<?php

namespace App\Services;

use Illuminate\Support\Arr;
use RuntimeException;

class CpanelEmailService
{
    public function __construct(
        private readonly ?string $host = null,
        private readonly ?string $username = null,
        private readonly ?string $token = null,
        private readonly ?string $domain = null,
        private readonly int $port = 2083,
        private readonly ?string $whmHost = null,
        private readonly ?string $whmUsername = null,
        private readonly ?string $whmToken = null,
        private readonly int $whmPort = 2087,
    ) {
    }

    public function isConfigured(): bool
    {
        return filled($this->host()) && filled($this->username()) && filled($this->token()) && filled($this->domain());
    }

    public function domain(): string
    {
        return (string) ($this->domain ?: config('services.cpanel.domain'));
    }

    public function listAccounts(): array
    {
        $response = $this->request('Email/list_pops', [
            'domain' => $this->domain(),
        ]);

        return Arr::get($response, 'data', []);
    }

    public function createAccount(string $email, string $password, int $quota = 1024): array
    {
        [$localPart, $domain] = $this->splitEmail($email);

        return $this->request('Email/add_pop', [
            'email' => $localPart,
            'domain' => $domain,
            'password' => $password,
            'quota' => $quota,
        ]);
    }

    public function deleteAccount(string $email): array
    {
        [$localPart, $domain] = $this->splitEmail($email);

        return $this->request('Email/delete_pop', [
            'email' => $localPart,
            'domain' => $domain,
        ]);
    }

    public function changePassword(string $email, string $newPassword): array
    {
        [$localPart, $domain] = $this->splitEmail($email);

        return $this->request('Email/passwd_pop', [
            'email' => $localPart,
            'domain' => $domain,
            'password' => $newPassword,
        ]);
    }

    public function webmailLoginUrl(?string $email = null): string
    {
        $host = $this->host() ?: $this->domain();

        $url = sprintf('https://%s:%d/', $host, $this->webmailPort());

        if ($email) {
            $url .= '?' . http_build_query(['user' => $this->normalizeEmail($email)]);
        }

        return $url;
    }

    public function createWebmailAutoLoginUrl(string $email): string
    {
        $login = $this->createWebmailAutoLogin($email);

        if (($login['method'] ?? 'get') === 'get') {
            return (string) $login['url'];
        }

        throw new RuntimeException('Webmail requires a POST login. Use createWebmailAutoLogin() and submit the returned form data.');
    }

    public function createWebmailAutoLogin(string $email): array
    {
        $email = $this->normalizeEmail($email);

        if ($this->isWhmConfigured()) {
            return [
                'method' => 'get',
                'url' => $this->createWhmWebmailSessionUrl($email),
                'fields' => [],
            ];
        }

        return $this->createWebmailSessionPostData($email);
    }

    public function canCreateDirectWebmailLogin(): bool
    {
        return true;
    }

    public function createWebmailSessionUrl(string $email): string
    {
        $postData = $this->createWebmailSessionPostData($email);

        return (string) $postData['url'];
    }

    public function createWebmailSessionPostData(string $email): array
    {
        [$localPart, $domain] = $this->splitEmail($email);

        // cPanel's Session::create_webmail_session_for_mail_user returns a
        // session value that MUST be submitted with an HTTP POST to Webmail's
        // /login endpoint. Redirecting to the session string directly only
        // shows the Webmail login page again.
        $response = $this->request('Session/create_webmail_session_for_mail_user', [
            'login' => $localPart,
            'domain' => $domain,
        ]);

        $data = Arr::get($response, 'data');
        if (!is_array($data)) {
            $data = Arr::get($response, 'result.data', []);
        }

        $session = Arr::get($data, 'session') ?: Arr::get($response, 'session');
        $token = Arr::get($data, 'token') ?: Arr::get($response, 'token') ?: '';
        $hostname = Arr::get($data, 'hostname') ?: $this->host();

        if (!$session || !is_string($session)) {
            throw new RuntimeException('cPanel did not return a Webmail session token.');
        }

        $token = trim((string) $token);
        if ($token !== '' && !str_starts_with($token, '/')) {
            $token = '/' . $token;
        }

        return [
            'method' => 'post',
            'url' => sprintf('https://%s:%d%s/login', $hostname, $this->webmailPort(), $token),
            'fields' => [
                'session' => $session,
            ],
        ];
    }

    private function createWhmWebmailSessionUrl(string $email): string
    {
        [$localPart, $domain] = $this->splitEmail($email);

        // For WHM's create_user_session + webmaild, pass the mailbox local part
        // as user and the mailbox domain separately. Passing the full address
        // with domain makes WHM look for user@domain@domain.
        $response = $this->whmRequest('create_user_session', [
            'user' => $localPart,
            'domain' => $domain,
            'service' => 'webmaild',
        ]);

        $url = Arr::get($response, 'data.url')
            ?: Arr::get($response, 'data.session')
            ?: Arr::get($response, 'url');

        if (!$url || !is_string($url)) {
            throw new RuntimeException('WHM did not return a direct Webmail session URL.');
        }

        if (str_starts_with($url, 'http://') || str_starts_with($url, 'https://')) {
            return $url;
        }

        $host = $this->whmHost() ?: $this->host();

        return sprintf('https://%s:%d/%s', $host, $this->webmailPort(), ltrim($url, '/'));
    }

    private function whmRequest(string $endpoint, array $query = []): array
    {
        if (!$this->isWhmConfigured()) {
            throw new RuntimeException('Direct Webmail login requires WHM_HOST, WHM_USERNAME, and WHM_API_TOKEN in .env.');
        }

        $query = array_merge(['api.version' => 1], $query);
        $url = sprintf('https://%s:%d/json-api/%s?%s', $this->whmHost(), $this->whmPort(), $endpoint, http_build_query($query));

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_HTTPHEADER => [
                'Authorization: whm ' . $this->whmUsername() . ':' . $this->whmToken(),
                'Accept: application/json',
            ],
        ]);

        $body = curl_exec($ch);
        $curlError = curl_error($ch);
        $statusCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($body === false) {
            throw new RuntimeException('WHM API request failed: ' . $curlError);
        }

        $decoded = json_decode($body, true);
        if (!is_array($decoded)) {
            throw new RuntimeException('WHM API returned an invalid response. HTTP status: ' . $statusCode);
        }

        $status = Arr::get($decoded, 'metadata.result')
            ?? Arr::get($decoded, 'metadata.command')
            ?? Arr::get($decoded, 'status');

        if ($statusCode >= 400 || (isset($decoded['metadata']['result']) && (int) $decoded['metadata']['result'] !== 1)) {
            $message = Arr::get($decoded, 'metadata.reason')
                ?: Arr::get($decoded, 'errors.0')
                ?: Arr::get($decoded, 'messages.0')
                ?: 'Unknown WHM API error.';
            throw new RuntimeException($message);
        }

        return $decoded;
    }

    private function request(string $endpoint, array $query = []): array
    {
        if (!$this->isConfigured()) {
            throw new RuntimeException('cPanel API is not configured. Please set CPANEL_HOST, CPANEL_USERNAME, CPANEL_API_TOKEN, and CPANEL_DOMAIN in .env.');
        }

        $url = sprintf('https://%s:%d/execute/%s?%s', $this->host(), $this->port(), $endpoint, http_build_query($query));

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_HTTPHEADER => [
                'Authorization: cpanel ' . $this->username() . ':' . $this->token(),
                'Accept: application/json',
            ],
        ]);

        $body = curl_exec($ch);
        $curlError = curl_error($ch);
        $statusCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($body === false) {
            throw new RuntimeException('cPanel API request failed: ' . $curlError);
        }

        $decoded = json_decode($body, true);
        if (!is_array($decoded)) {
            throw new RuntimeException('cPanel API returned an invalid response. HTTP status: ' . $statusCode);
        }

        if ($statusCode >= 400 || (isset($decoded['status']) && (int) $decoded['status'] !== 1)) {
            $message = Arr::get($decoded, 'errors.0')
                ?: Arr::get($decoded, 'messages.0')
                ?: Arr::get($decoded, 'metadata.reason')
                ?: 'Unknown cPanel API error.';
            throw new RuntimeException($message);
        }

        return $decoded;
    }

    private function normalizeEmail(string $email): string
    {
        [$localPart, $domain] = $this->splitEmail($email);

        return $localPart . '@' . $domain;
    }

    private function splitEmail(string $email): array
    {
        $email = strtolower(trim($email));
        $configuredDomain = strtolower(trim($this->domain()));

        if (str_contains($email, '@')) {
            $localPart = strtok($email, '@') ?: '';

            // Always use the configured domain for this admin page. This also
            // collapses accidental values like user@domain.com@domain.com back
            // to user + domain before any cPanel/WHM request is made.
            $domain = $configuredDomain;

            if ($domain === '') {
                $afterAt = substr(strstr($email, '@'), 1);
                $domain = explode('@', $afterAt)[0] ?? '';
            }

            return [$localPart, $domain];
        }

        return [$email, $configuredDomain];
    }

    private function host(): string
    {
        return trim((string) ($this->host ?: config('services.cpanel.host')));
    }

    private function username(): string
    {
        return trim((string) ($this->username ?: config('services.cpanel.username')));
    }

    private function token(): string
    {
        return trim((string) ($this->token ?: config('services.cpanel.api_token')));
    }

    private function port(): int
    {
        return (int) ($this->port ?: config('services.cpanel.port', 2083));
    }

    private function isWhmConfigured(): bool
    {
        return filled($this->whmHost()) && filled($this->whmUsername()) && filled($this->whmToken());
    }

    private function whmHost(): string
    {
        return trim((string) ($this->whmHost ?: config('services.cpanel.whm_host')));
    }

    private function whmUsername(): string
    {
        return trim((string) ($this->whmUsername ?: config('services.cpanel.whm_username')));
    }

    private function whmToken(): string
    {
        return trim((string) ($this->whmToken ?: config('services.cpanel.whm_api_token')));
    }

    private function whmPort(): int
    {
        return (int) ($this->whmPort ?: config('services.cpanel.whm_port', 2087));
    }

    private function webmailPort(): int
    {
        return (int) config('services.cpanel.webmail_port', 2096);
    }
}
