<?php

declare(strict_types=1);

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

use FecaMailshots\Application\DslCompiler;
use FecaMailshots\Application\DslValidator;
use FecaMailshots\Domain\DslParser;
use FecaMailshots\Tests\Unit\FakeMetadataProvider;

$metadata = new FakeMetadataProvider([
    'advertisers' => ['AdvertiserName', 'Selected', 'IsLapsed?'],
    'ads' => ['ID', 'Advertiser', 'AdSize', 'Price', 'PageID', 'State', 'Notes'],
    'pages' => ['ID', 'Issue', 'Page'],
    'articles' => ['ID', 'PageID', 'ArticleNumber', 'Content', 'MemberName', 'Author', 'DCN', 'ArticleWords', 'OtherWords', 'OtherContent'],
    'issues' => ['ID', 'IssueMonths', 'Description'],
    'invoices' => ['id', 'issue', 'issue_id', 'ad_id', 'invoice_number', 'status'],
    'contacts' => ['id', 'account_id', 'last_name', 'contact_email_1', 'is_fen_1', 'is_deleted'],
    'accounts' => ['id', 'name', 'is_deleted'],
]);

$parser = new DslParser();
$validator = new DslValidator($metadata);
$compiler = new DslCompiler($metadata);

$cases = [
    'ads and pages where issue(202605)' => [
        'needle' => 's_pages.`Issue` = ?',
        'params' => [202605],
    ],
    'articles and pages where issue(202605)' => [
        'needle' => 's_pages.`Issue` = ?',
        'params' => [202605],
    ],
    'advertisers and ads where selected' => [
        'needle' => 'COALESCE(s_advertisers.`Selected`, 0) <> 0',
        'params' => [],
    ],
    'invoices and ads where invoice-ids(10, 11)' => [
        'needle' => 's_invoices.`id` IN (?,?)',
        'params' => [10, 11],
    ],
];

foreach ($cases as $dsl => $expect) {
    $ast = $parser->parse($dsl);
    $validation = $validator->validate($ast);
    if (($validation['errors'] ?? []) !== []) {
        fwrite(STDERR, "Expected valid DSL {$dsl}: " . json_encode($validation['errors']) . "\n");
        exit(1);
    }
    $compiled = $compiler->compile($ast);
    $sql = (string) ($compiled['sql'] ?? '');
    if (strpos($sql, $expect['needle']) === false) {
        fwrite(STDERR, "Expected SQL for {$dsl} to contain {$expect['needle']}\n{$sql}\n");
        exit(1);
    }
    if (($compiled['params'] ?? []) !== $expect['params']) {
        fwrite(STDERR, "Unexpected params for {$dsl}: " . json_encode($compiled['params'] ?? []) . "\n");
        exit(1);
    }
}

$projectionDsl = 'advertisers and ads where issue(125)';
$projectionAst = $parser->parse($projectionDsl);
$projectionValidation = $validator->validate($projectionAst);
if (($projectionValidation['errors'] ?? []) !== []) {
    fwrite(STDERR, "Expected valid DSL {$projectionDsl}: " . json_encode($projectionValidation['errors']) . "\n");
    exit(1);
}
$projection = $compiler->compile($projectionAst);
$projectionSql = (string) ($projection['sql'] ?? '');
foreach ([
    'advertisers.AdvertiserName',
    'advertisers.Selected',
    'advertisers.IsLapsed?',
    'ads.ID',
    'ads.Advertiser',
    'ads.AdSize',
    'ads.Price',
    'ads.PageID',
    'ads.State',
    'ads.Notes',
] as $qualifiedField) {
    if (strpos($projectionSql, ' AS `' . $qualifiedField . '`') === false) {
        fwrite(STDERR, "Expected projection for {$projectionDsl} to include {$qualifiedField}\n{$projectionSql}\n");
        exit(1);
    }
}

$quotedFieldDsl = 'advertisers where advertisers.`IsLapsed?` = true';
$quotedFieldAst = $parser->parse($quotedFieldDsl);
$quotedFieldValidation = $validator->validate($quotedFieldAst);
if (($quotedFieldValidation['errors'] ?? []) !== []) {
    fwrite(STDERR, "Expected valid DSL {$quotedFieldDsl}: " . json_encode($quotedFieldValidation['errors']) . "\n");
    exit(1);
}
$quotedFieldSql = (string) ($compiler->compile($quotedFieldAst)['sql'] ?? '');
if (strpos($quotedFieldSql, 's_advertisers.`IsLapsed?` = ?') === false) {
    fwrite(STDERR, "Expected quoted physical field to compile directly\n{$quotedFieldSql}\n");
    exit(1);
}

$issueCases = [
    'issues where issue(202605)' => 's_issues.`ID` = ?',
    'articles where issue(202605)' => 'p_article_issue.`Issue` = ?',
    'ads where issue(202605)' => 'p_ad_issue.`Issue` = ?',
    'advertisers where issue(202605)' => 'p_adv_issue.`Issue` = ?',
    'invoices where issue(202605)' => 's_invoices.`issue_id` = ?',
    'contacts and accounts and issues where fen1-contact and issue(202605)' => 'CROSS JOIN `issues` AS s_issues',
];
foreach ($issueCases as $dsl => $needle) {
    $ast = $parser->parse($dsl);
    $validation = $validator->validate($ast);
    if (($validation['errors'] ?? []) !== []) {
        fwrite(STDERR, "Expected valid DSL {$dsl}: " . json_encode($validation['errors']) . "\n");
        exit(1);
    }
    $sql = (string) ($compiler->compile($ast)['sql'] ?? '');
    if (strpos($sql, $needle) === false) {
        fwrite(STDERR, "Expected issue filter SQL for {$dsl} to contain {$needle}\n{$sql}\n");
        exit(1);
    }
}

$softDeleteDsl = 'contacts and accounts where fen1-contact';
$softDeleteAst = $parser->parse($softDeleteDsl);
$softDeleteValidation = $validator->validate($softDeleteAst);
if (($softDeleteValidation['errors'] ?? []) !== []) {
    fwrite(STDERR, "Expected valid DSL {$softDeleteDsl}: " . json_encode($softDeleteValidation['errors']) . "\n");
    exit(1);
}
$softDeleteSql = (string) ($compiler->compile($softDeleteAst)['sql'] ?? '');
foreach ([
    '(s_contacts.`is_deleted` IS NULL OR s_contacts.`is_deleted` + 0 = 0)',
    '(s_accounts.`is_deleted` IS NULL OR s_accounts.`is_deleted` + 0 = 0)',
] as $needle) {
    if (strpos($softDeleteSql, $needle) === false) {
        fwrite(STDERR, "Expected soft-delete predicate {$needle}\n{$softDeleteSql}\n");
        exit(1);
    }
}

$articleExclusionDsl = 'contacts and accounts and issues where fen1-contact and issue(125) and member-or-affiliate-or-parish-council and not account-has-article-in-issue(125)';
$articleExclusionAst = $parser->parse($articleExclusionDsl);
$articleExclusionValidation = $validator->validate($articleExclusionAst);
if (($articleExclusionValidation['errors'] ?? []) !== []) {
    fwrite(STDERR, "Expected valid DSL {$articleExclusionDsl}: " . json_encode($articleExclusionValidation['errors']) . "\n");
    exit(1);
}
$articleExclusionCompiled = $compiler->compile($articleExclusionAst);
$articleExclusionSql = (string) ($articleExclusionCompiled['sql'] ?? '');
foreach ([
    'NOT (EXISTS (SELECT 1 FROM `articles` AS article_account_issue',
    'article_account_issue.`MemberName`',
    'COLLATE utf8mb4_unicode_ci',
    'page_account_issue.`Issue` = ?',
] as $needle) {
    if (strpos($articleExclusionSql, $needle) === false) {
        fwrite(STDERR, "Expected article exclusion SQL to contain {$needle}\n{$articleExclusionSql}\n");
        exit(1);
    }
}
if (($articleExclusionCompiled['params'] ?? []) !== [125, 125]) {
    fwrite(STDERR, "Unexpected article exclusion params: " . json_encode($articleExclusionCompiled['params'] ?? []) . "\n");
    exit(1);
}

$blankDsl = 'advertisers where advertisers.AdvertiserName blank';
$blankAst = $parser->parse($blankDsl);
$blankValidation = $validator->validate($blankAst);
if (($blankValidation['errors'] ?? []) !== []) {
    fwrite(STDERR, "Expected valid DSL {$blankDsl}: " . json_encode($blankValidation['errors']) . "\n");
    exit(1);
}
$blankSql = (string) ($compiler->compile($blankAst)['sql'] ?? '');
if (strpos($blankSql, "(s_advertisers.`AdvertiserName` IS NULL OR s_advertisers.`AdvertiserName` = '')") === false) {
    fwrite(STDERR, "Expected blank field test to compile as null-or-empty check\n{$blankSql}\n");
    exit(1);
}

$notBlankDsl = 'advertisers where advertisers.AdvertiserName != blank';
$notBlankAst = $parser->parse($notBlankDsl);
$notBlankValidation = $validator->validate($notBlankAst);
if (($notBlankValidation['errors'] ?? []) !== []) {
    fwrite(STDERR, "Expected valid DSL {$notBlankDsl}: " . json_encode($notBlankValidation['errors']) . "\n");
    exit(1);
}
$notBlankSql = (string) ($compiler->compile($notBlankAst)['sql'] ?? '');
if (strpos($notBlankSql, "(s_advertisers.`AdvertiserName` IS NOT NULL AND s_advertisers.`AdvertiserName` <> '')") === false) {
    fwrite(STDERR, "Expected != blank field test to compile as not-null-and-not-empty check\n{$notBlankSql}\n");
    exit(1);
}

echo "DSL FEN built-in tests passed\n";
