<?php

declare(strict_types=1);

require_once dirname(__DIR__, 2) . '/feca_mailshots_plugin/src/autoload.php';

use FecaMailshots\Application\DataSourceService;
use FecaMailshots\Application\DslCompiler;
use FecaMailshots\Application\DslValidator;
use FecaMailshots\Application\ImapAppender;
use FecaMailshots\Application\MailCredentialsProvider;
use FecaMailshots\Application\MailshotRunService;
use FecaMailshots\Application\SmtpSender;
use FecaMailshots\Application\TemplateRenderer;
use FecaMailshots\Domain\DslParser;
use FecaMailshots\Infrastructure\DatabaseSourceMetadataProvider;
use FecaMailshots\Infrastructure\Env;
use FecaMailshots\Infrastructure\PdoDatabaseRouter;
use FecaMailshots\Repository\LastRunRepository;
use FecaMailshots\Repository\AttachmentRepository;
use FecaMailshots\Repository\MailshotQueryRepository;
use FecaMailshots\Repository\MailshotRepository;

final class FakeCreds implements MailCredentialsProvider {
    public function credentials(): ?array {
        return [
            'smtp_host' => 'smtp.test.local',
            'smtp_port' => 587,
            'smtp_user' => 'user',
            'smtp_password' => 'pass',
            'smtp_from_email' => 'editor@example.org',
            'smtp_from_name' => 'Editor',
            'smtp_require_tls' => false,
            'imap_host' => 'imap.test.local',
            'imap_port' => 993,
            'imap_user' => 'user',
            'imap_password' => 'pass',
            'imap_sent_folder' => 'Sent',
            'imap_mailbox_flags' => '/imap/ssl',
        ];
    }
}

final class FakeSmtp implements SmtpSender {
    public int $count = 0;
    /** @var list<array{filename:string,mime_type:string,content_bytes:string}> */
    public array $lastAttachments = [];
    public function send(array $credentials, array $to, array $cc, array $bcc, string $subject, string $htmlBody, ?string $replyTo = null, array $attachments = []): array {
        $this->count++;
        $this->lastAttachments = $attachments;
        if ($to === []) {
            throw new RuntimeException('No recipients');
        }
        return ['raw_mime' => "To: " . implode(',', $to) . "\r\nSubject: {$subject}\r\n\r\n{$htmlBody}"];
    }
}

final class FakeImap implements ImapAppender {
    public int $count = 0;
    public function appendSent(array $credentials, string $rawMime, string $attemptId): void {
        $this->count++;
    }
}

Env::load(dirname(__DIR__, 2) . '/credentials/.env');

$dbConfig = [
    'MYSQL_HOST' => '127.0.0.1',
    'MYSQL_PORT' => (string) (getenv('MYSQL_TUNNEL_LOCAL_PORT') ?: '13306'),
    'MYSQL_USER' => Env::require('REMOTE_MYSQL_USER'),
    'MYSQL_PASSWORD' => Env::require('REMOTE_MYSQL_PASSWORD'),
    'MAILSHOTS_REMOTE_MYSQL_DB' => Env::require('MAILSHOTS_REMOTE_MYSQL_DB'),
    'MEMBERS_REMOTE_MYSQL_DB' => Env::require('MEMBERS_REMOTE_MYSQL_DB'),
    'FEN_REMOTE_MYSQL_DB' => Env::require('FEN_REMOTE_MYSQL_DB'),
];

$router = new PdoDatabaseRouter($dbConfig);
$metadata = new DatabaseSourceMetadataProvider($router);
$parser = new DslParser();
$validator = new DslValidator($metadata);
$compiler = new DslCompiler($metadata);
$queryRepo = new MailshotQueryRepository($router);
$mailshotRepo = new MailshotRepository($router);
$attachmentRepo = new AttachmentRepository($router);
$dataSourceService = new DataSourceService($queryRepo, $router, $parser, $validator, $compiler, $metadata, $mailshotRepo);
$lastRunRepo = new LastRunRepository($router);

$smtp = new FakeSmtp();
$imap = new FakeImap();
$run = new MailshotRunService(
    $mailshotRepo,
    $queryRepo,
    $attachmentRepo,
    $dataSourceService,
    new TemplateRenderer(),
    $smtp,
    $imap,
    new FakeCreds(),
    $lastRunRepo
);

$membersDb = $router->membersDbName();
$stmt = $router->membersPdo()->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = :schema ORDER BY table_name ASC LIMIT 1');
$stmt->execute(['schema' => $membersDb]);
$table = $stmt->fetchColumn();
if (!is_string($table) || $table === '') {
    fwrite(STDERR, "No source table in members db\n");
    exit(1);
}

$dsl = $membersDb . '.' . $table;
$uniq = gmdate('Ymd_His') . '_' . bin2hex(random_bytes(3));
$queryName = 'phase4_ds_' . $uniq;
$queryId = null;
$mailshotId = null;
$attachmentId = null;
$attachmentName = 'phase4_att_' . $uniq;

try {
    $savedQ = $dataSourceService->save(null, $queryName, $dsl);
    if (($savedQ['ok'] ?? false) !== true) {
        fwrite(STDERR, 'Failed saving datasource: ' . json_encode($savedQ) . "\n");
        exit(1);
    }
    $queryId = (int) $savedQ['id'];

    $attachmentId = $attachmentRepo->create([
        'name' => $attachmentName,
        'file_name' => 'phase4-note.txt',
        'mime_type' => 'text/plain',
        'file_bytes' => 'phase4 attachment bytes',
    ]);

    $savedM = $mailshotRepo->create([
        'Purpose' => 'Phase4 ' . $uniq,
        'DataSource' => $queryName,
        'CC' => '',
        'BCC' => '',
        'Subject' => 'Hello {{ id|default(ID|default("recipient")) }}',
        'Message' => '<p>Hi {{ name|default(Name|default("there")) }}</p>',
        'PDFAttachment' => '<p>PDF {{ id|default("none") }}</p>',
        'AttachmentNames' => json_encode([$attachmentName], JSON_UNESCAPED_SLASHES),
        'PDFFilenameDerivedFrom' => '',
        'ReplyTo' => '',
    ]);
    $mailshotId = (int) $savedM;

    $render = $run->renderTest($mailshotId, 0);
    if (($render['ok'] ?? false) !== true) {
        fwrite(STDERR, 'renderTest failed: ' . json_encode($render) . "\n");
        exit(1);
    }

    $pdfBatch = $run->generatePdfBatch($mailshotId);
    if (($pdfBatch['ok'] ?? false) !== true) {
        fwrite(STDERR, 'generatePdfBatch failed: ' . json_encode($pdfBatch) . "\n");
        exit(1);
    }
    if ((int) ($pdfBatch['generated_count'] ?? 0) <= 0) {
        fwrite(STDERR, "generatePdfBatch should generate at least one PDF\n");
        exit(1);
    }
    if (strpos((string) ($pdfBatch['merged_pdf_bytes'] ?? ''), '%PDF') !== 0) {
        fwrite(STDERR, "generatePdfBatch merged_pdf_bytes should be a PDF payload\n");
        exit(1);
    }

    $sendBlank = $run->sendTest($mailshotId, 0, '');
    if (($sendBlank['ok'] ?? true) !== false) {
        fwrite(STDERR, "sendTest blank email should fail\n");
        exit(1);
    }

    $sendTest = $run->sendTest($mailshotId, 0, 'receiver@example.org');
    if (($sendTest['ok'] ?? false) !== true) {
        fwrite(STDERR, 'sendTest failed: ' . json_encode($sendTest) . "\n");
        exit(1);
    }
    if (array_key_exists('rendered', $sendTest)) {
        fwrite(STDERR, "sendTest should not return rendered payload\n");
        exit(1);
    }
    if (count($smtp->lastAttachments) < 2) {
        fwrite(STDERR, "sendTest should include static attachment and rendered PDF attachment\n");
        exit(1);
    }
    $mimeTypes = array_map(static fn(array $a): string => (string) ($a['mime_type'] ?? ''), $smtp->lastAttachments);
    if (!in_array('application/pdf', $mimeTypes, true)) {
        fwrite(STDERR, "sendTest should include a rendered application/pdf attachment\n");
        exit(1);
    }
    if (!in_array('text/plain', $mimeTypes, true)) {
        fwrite(STDERR, "sendTest should include configured text/plain attachment\n");
        exit(1);
    }
    $pdfAttachment = null;
    foreach ($smtp->lastAttachments as $att) {
        if ((string) ($att['mime_type'] ?? '') === 'application/pdf') {
            $pdfAttachment = $att;
            break;
        }
    }
    if (!is_array($pdfAttachment) || strpos((string) ($pdfAttachment['content_bytes'] ?? ''), '%PDF') !== 0) {
        fwrite(STDERR, "sendTest PDF attachment content is not a PDF payload\n");
        exit(1);
    }

    // Regression: configured-but-missing attachment must return a controlled error, not throw/fatal.
    $mailshotRepo->update($mailshotId, [
        'Purpose' => 'Phase4 ' . $uniq,
        'DataSource' => $queryName,
        'CC' => '',
        'BCC' => '',
        'Subject' => 'Hello {{ id|default(ID|default("recipient")) }}',
        'Message' => '<p>Hi {{ name|default(Name|default("there")) }}</p>',
        'PDFAttachment' => '<p>PDF {{ id|default("none") }}</p>',
        'AttachmentNames' => json_encode(['missing_attachment_name'], JSON_UNESCAPED_SLASHES),
        'PDFFilenameDerivedFrom' => '',
        'ReplyTo' => '',
        'RecipientEmailField' => '',
    ]);
    $sendMissing = $run->sendTest($mailshotId, 0, 'receiver@example.org');
    if (($sendMissing['ok'] ?? true) !== false) {
        fwrite(STDERR, "sendTest should fail when configured attachment is missing\n");
        exit(1);
    }
    $missingText = implode('; ', (array) ($sendMissing['errors'] ?? []));
    if (strpos($missingText, 'Attachment "missing_attachment_name" not found.') === false) {
        fwrite(STDERR, "sendTest missing-attachment error text mismatch: {$missingText}\n");
        exit(1);
    }

    $runRes = $run->runMailshot($mailshotId);
    if (($runRes['ok'] ?? false) !== true) {
        fwrite(STDERR, 'runMailshot failed: ' . json_encode($runRes) . "\n");
        exit(1);
    }
    if ((int) ($runRes['attempted'] ?? 0) <= 0) {
        fwrite(STDERR, "runMailshot attempted should be > 0\n");
        exit(1);
    }

    $rows = $lastRunRepo->listForMailshot($mailshotId);
    if (count($rows) !== (int) $runRes['attempted']) {
        fwrite(STDERR, 'lastRun row count mismatch\n');
        exit(1);
    }

    $retry = $run->retryFailed($mailshotId);
    if (($retry['ok'] ?? false) !== true) {
        fwrite(STDERR, 'retryFailed failed: ' . json_encode($retry) . "\n");
        exit(1);
    }

    echo "Phase 4 run-flow integration test passed\n";
    echo "attempted=" . (int) $runRes['attempted'] . "\n";
    echo "smtp_calls={$smtp->count}\n";
    echo "imap_calls={$imap->count}\n";
} finally {
    if ($mailshotId !== null) {
        try { $lastRunRepo->clearForMailshot($mailshotId); } catch (Throwable $e) {}
        try { $mailshotRepo->delete($mailshotId); } catch (Throwable $e) {}
    }
    if ($attachmentId !== null) {
        try { $attachmentRepo->delete($attachmentId); } catch (Throwable $e) {}
    }
    if ($queryId !== null) {
        try { $queryRepo->delete($queryId); } catch (Throwable $e) {}
    }
}
