<?php

namespace App\Jobs;

use App\Models\MailProfile;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;

class TestMailProfileConnection implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $timeout = 30;

    public function __construct(public int $mailProfileId) {}

    public function handle(): void
    {
        $profile = MailProfile::find($this->mailProfileId);
        if (! $profile) {
            return;
        }

        try {
            $this->performSmtpAuthTest($profile);
            $profile->forceFill([
                'test_status' => 'success',
                'test_checked_at' => now(),
                'last_success_at' => now(),
                'last_error_message' => null,
            ])->save();
        } catch (\Throwable $e) {
            Log::warning('mail_profile.test_failed', [
                'id' => $profile->id,
                'host' => $profile->host,
                'port' => $profile->port,
                'encryption' => $profile->encryption,
                // Intentionally NOT logging username/password
                'error' => $e->getMessage(),
            ]);
            $profile->forceFill([
                'test_status' => 'failed',
                'test_checked_at' => now(),
                'last_error_at' => now(),
                'last_error_message' => substr($e->getMessage(), 0, 400),
            ])->save();
        }
    }

    /**
     * Perform a real SMTP handshake + (optional) STARTTLS + AUTH LOGIN cycle.
     * Throws on any protocol / auth failure.
     */
    protected function performSmtpAuthTest(MailProfile $profile): void
    {
        $host = $profile->host;
        $port = (int) $profile->port;
        $encryption = strtolower((string) ($profile->encryption ?? ''));
        $username = $profile->username;
        $password = trim($profile->decryptPassword() ?? '');
        if (app()->environment('local')) {
            Log::debug('mail_profile.test.debug_password_length', [
                'id' => $profile->id,
                'len' => strlen($password),
                'empty' => $password === '',
            ]);
        }

        if ($username === '' || $password === '') {
            throw new \RuntimeException('Missing username or password (decryption may have failed or no password set)');
        }

        $remote = ($encryption === 'ssl') ? 'ssl://'.$host : $host;
        $errno = 0;
        $errstr = '';
        $socket = @fsockopen($remote, $port, $errno, $errstr, 15);
        if (! $socket) {
            throw new \RuntimeException("Connect failed: $errstr ($errno)");
        }
        try {
            stream_set_timeout($socket, 15);
            $this->expect($socket, [220], 'greeting');

            $ehloDomain = gethostname() ?: 'localhost';
            $this->command($socket, "EHLO $ehloDomain\r\n", [250], 'EHLO');

            if ($encryption === 'tls') {
                $this->command($socket, "STARTTLS\r\n", [220], 'STARTTLS');
                if (! stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
                    throw new \RuntimeException('STARTTLS negotiation failed');
                }
                // Re-issue EHLO after TLS per RFC
                $this->command($socket, "EHLO $ehloDomain\r\n", [250], 'post-STARTTLS EHLO');
            }

            // AUTH LOGIN flow
            $this->command($socket, "AUTH LOGIN\r\n", [334], 'AUTH LOGIN');
            $this->command($socket, base64_encode($username)."\r\n", [334], 'AUTH username');
            $authResp = $this->command($socket, base64_encode($password)."\r\n", [235], 'AUTH password');

            // Cleanly quit
            $this->command($socket, "QUIT\r\n", [221], 'QUIT');
        } finally {
            try {
                fclose($socket);
            } catch (\Throwable) {
                // ignore
            }
        }
    }

    /**
     * Send a command and assert expected code(s). Returns the last response line.
     */
    protected function command($socket, string $cmd, array $expect, string $context): string
    {
        fwrite($socket, $cmd);

        return $this->expect($socket, $expect, $context);
    }

    /**
     * Read lines until final line (code + space). Validate code.
     */
    protected function expect($socket, array $expectedCodes, string $context): string
    {
        $lines = [];
        while (true) {
            $line = fgets($socket, 2048);
            if ($line === false) {
                throw new \RuntimeException("SMTP read failure during $context");
            }
            $lines[] = rtrim($line, "\r\n");
            if (preg_match('/^(\d{3})([ -])/', $line, $m)) {
                $code = (int) $m[1];
                $more = $m[2] === '-';
                if (! $more) {
                    if (! in_array($code, $expectedCodes, true)) {
                        throw new \RuntimeException("Unexpected SMTP code $code during $context: ".implode(' | ', $lines));
                    }

                    return $line;
                }
            }
            if (count($lines) > 50) {
                throw new \RuntimeException('SMTP response too verbose');
            }
        }
    }
}
