<?php

namespace Tests\Feature;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;

/**
 * Campaign edit Save must keep XML publisher whitelist (`affiliates`) and
 * other unmodeled targeting keys (`pop_type`) instead of replacing the JSON
 * from an empty form-built array.
 *
 * Run with the feature bootstrap and newrelic disabled:
 *   php8.2 -d newrelic.enabled=0 -d newrelic.daemon.dont_launch=3 \
 *     vendor/bin/phpunit --bootstrap tests/feature_bootstrap.php tests/feature/CampaignUpdatePreservesAffiliatesTest.php
 */
final class CampaignUpdatePreservesAffiliatesTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    /** Throwaway advertiser id — keeps the run isolated from real data. */
    private const ADV = 999999990;

    private array $sess;

    /** @var \CodeIgniter\Database\BaseConnection */
    private $conn;

    /** @var list<int> */
    private array $campaignIds = [];

    /** UNIX timestamp floor for Shorty.adcenter_log lookups this run. */
    private int $logSince = 0;

    protected function setUp(): void
    {
        parent::setUp();

        $this->sess = [
            'logged_in' => 1,
            'adv_id'    => self::ADV,
            'user_id'   => self::ADV,
            'username'  => 'QA Bot',
            'role'      => 'admin',
        ];

        $filters = config('Filters');
        unset($filters->filters['csrf']);

        $this->wireShortyFromOci();
        $this->logSince = time() - 5;

        $this->conn = \Config\Database::connect('default');
        $this->cleanup();
    }

    protected function tearDown(): void
    {
        $this->cleanup();
        parent::tearDown();
    }

    private function cleanup(): void
    {
        $camps = $this->conn->table('campaign')->select('id')
            ->where('advertiser_id', self::ADV)->get()->getResultArray();
        foreach ($camps as $r) {
            $cid = (int) $r['id'];
            $this->conn->table('campaign_targeting')->where('campaign_id', $cid)->delete();
            $this->conn->table('campaign_creative_link')->where('campaign_id', $cid)->delete();
        }
        $this->conn->table('campaign')->where('advertiser_id', self::ADV)->delete();

        foreach ($this->campaignIds as $cid) {
            $this->deleteLogRows($cid);
        }
        $this->campaignIds = [];
    }

    /**
     * Feature bootstrap only wires the Admin group. Point the Shorty group at
     * the same oci ini the app uses in this environment so EventLogService
     * (and the assertion below) can reach adcenter_log without committing creds.
     */
    private function wireShortyFromOci(): void
    {
        $ini = @parse_ini_file('/usr/share/php/shorty_oci.ini');
        if (!is_array($ini) || empty($ini['host'])) {
            return;
        }
        $cfg = config('Database');
        $cfg->shorty['hostname'] = $ini['host'];
        $cfg->shorty['username'] = $ini['user'] ?? '';
        $cfg->shorty['password'] = $ini['pass'] ?? '';
        $cfg->shorty['database'] = $ini['db'] ?? 'Shorty';
    }

    private function deleteLogRows(int $campaignId): void
    {
        try {
            \Config\Database::connect('shorty')->table('adcenter_log')
                ->where('entity_id', $campaignId)
                ->where('entity', 'Campaign')
                ->where('time_stamp >=', $this->logSince)
                ->delete();
        } catch (\Throwable $e) {
            // Shorty is optional in this bootstrap; skip log cleanup if unreachable.
        }
    }

    private function req()
    {
        return $this->withSession($this->sess);
    }

    public function testUpdateKeepsAffiliatesAndPopTypeAndWritesEventLog(): void
    {
        $this->conn->table('campaign')->insert([
            'advertiser_id'   => self::ADV,
            'name'            => 'QA XML Whitelist Campaign',
            'status'          => 'A',
            'bid'             => '0.100000',
            'budget'          => '50.00',
            'campaign_type'   => 2,
            'campaign_mode'   => 'CPC',
            'xml_traffic'     => 1,
            'duration'        => 0,
            'linktrust_pixel' => '',
            'site'            => '',
            'pixel'           => '',
            'pixel_conv'      => '',
            'system'          => 0,
            'system_id'       => 0,
            'usonly'          => 0,
        ]);
        $id = (int) $this->conn->insertID();
        $this->campaignIds[] = $id;

        $this->conn->table('campaign_targeting')->insert([
            'campaign_id' => $id,
            'json'        => json_encode([
                'affiliates' => ['120438'],
                'pop_type'   => 'tab',
                'countries'  => ['USA'],
            ]),
        ]);

        $this->req()->post('campaign/update/' . $id, [
            'name'   => 'QA XML Whitelist Campaign',
            'bid'    => '0.25',
            'budget' => '25',
            'status' => 'A',
        ]);

        $row = $this->conn->table('campaign')->where('id', $id)->get()->getRowArray();
        $this->assertNotNull($row, 'campaign row should still exist');
        $this->assertEqualsWithDelta(0.25, (float) $row['bid'], 0.0001);
        $this->assertEqualsWithDelta(25.0, (float) $row['budget'], 0.0001);
        $this->assertSame('A', $row['status']);

        $targetingRow = $this->conn->table('campaign_targeting')
            ->where('campaign_id', $id)->get()->getRowArray();
        $this->assertNotNull($targetingRow, 'targeting row should still exist');
        $json = json_decode((string) $targetingRow['json'], true);
        $this->assertIsArray($json);
        $this->assertSame(['120438'], array_map('strval', (array) ($json['affiliates'] ?? [])));
        $this->assertSame('tab', $json['pop_type'] ?? null);

        $log = $this->fetchLogRow($id);
        $this->assertNotNull($log, 'adcenter_log row should exist for the campaign update');
        $this->assertSame('Update', $log['action']);
        $this->assertSame((string) $id, (string) $log['entity_id']);
    }

    /**
     * @return array<string, mixed>|null
     */
    private function fetchLogRow(int $campaignId): ?array
    {
        try {
            $row = \Config\Database::connect('shorty')->table('adcenter_log')
                ->where('entity_id', $campaignId)
                ->where('entity', 'Campaign')
                ->where('time_stamp >=', $this->logSince)
                ->orderBy('id', 'DESC')
                ->limit(1)
                ->get()
                ->getRowArray();
            return $row ?: null;
        } catch (\Throwable $e) {
            $this->fail('shorty.adcenter_log is unreachable: ' . $e->getMessage());
        }
    }
}
