<?php

declare(strict_types=1);

namespace App\Commands;

use App\Services\Mobile\MobileJsonService;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use Config\Database;
use Throwable;

/**
 * Validasi staging: koneksi DB + perilaku MobileJsonService vs ekspektasi CI3.
 *
 * Opsi:
 *   --with-uploads   Uji upload kecil (save_pp) lalu hapus file & kembalikan photo DB jika perlu
 *
 * Env opsional (uji mutasi ringan):
 *   STAGING_VALIDATE_TOKEN   token pegawai valid
 *   STAGING_VALIDATE_PASS    password plaintext (untak uji save_password salah)
 */
class ApiStagingValidate extends BaseCommand
{
    protected $group       = 'Api';
    protected $name        = 'api:staging-validate';
    protected $usage       = 'api:staging-validate [--with-uploads]';
    protected $description = 'Validasi staging API mobile terhadap DB (parity CI3)';

    private int $passed = 0;

    private int $failed = 0;

    /** @var list<array{endpoint: string, ok: bool, note: string}> */
    private array $results = [];

    public function run(array $params)
    {
        $withUploads = CLI::getOption('with-uploads') !== null;

        CLI::write('=== API staging validate ===', 'yellow');
        CLI::write('Timezone: ' . date_default_timezone_get());
        CLI::write('API_PARITY_LOG: ' . (filter_var(env('API_PARITY_LOG', false), FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'));

        if (! $this->databaseReachable()) {
            $this->record('database_ping', false, 'Koneksi database gagal — periksa .env (samakan dengan CI3).');
            $this->finish($withUploads, false);

            return EXIT_ERROR;
        }
        $this->record('database_ping', true, 'SELECT 1 OK');

        $svc = new MobileJsonService();
        $db  = Database::connect();

        $this->assertLoginInvalid($svc);
        $this->assertLoginEmpty($svc);
        $this->assertLoginWTokenEmpty($svc);
        $this->assertLoginWTokenInvalid($svc);
        $this->assertProfilInvalid($svc);
        $this->assertPresensiTodayInvalid($svc);
        $this->assertPresensiInvalid($svc);
        $this->assertSaveMasukInvalidToken($svc);
        $this->assertSavePulangInvalidToken($svc);
        $this->assertSaveIstirahatInvalidToken($svc);
        $this->assertSaveAktifitasInvalidToken($svc);
        $this->assertSaveCutiInvalidToken($svc);
        $this->assertBatalkanCutiInvalidToken($svc);
        $this->assertBeritaInvalid($svc);
        $this->assertCutiInvalid($svc);
        $this->assertLemburInvalid($svc);
        $this->assertLiburInvalid($svc);
        $this->assertAktifitasInvalid($svc);
        $this->assertDaftarTodayInvalid($svc);
        $this->assertSavePpInvalidToken($svc);
        $this->assertSavePasswordInvalidToken($svc);

        $token = $this->fetchSampleToken($db);
        if ($token === null) {
            $this->record('authenticated_read_tests', true, 'Lewat: tidak ada pegawai dengan token di DB (uji invalid token sudah jalan).');
        } else {
            $this->runAuthenticatedSuite($svc, $token, $withUploads);
        }

        $this->assertUploadDirectories();

        $allOk = $this->failed === 0;
        $this->finish($withUploads, $allOk);

        return $allOk ? EXIT_SUCCESS : EXIT_ERROR;
    }

    private function finish(bool $withUploads, bool $allOk): void
    {
        $path = WRITEPATH . 'staging' . DIRECTORY_SEPARATOR;
        if (! is_dir($path)) {
            mkdir($path, 0755, true);
        }

        $report = [
            'generated_at' => date('c'),
            'timezone'     => date_default_timezone_get(),
            'with_uploads' => $withUploads,
            'passed'       => $this->passed,
            'failed'       => $this->failed,
            'all_ok'       => $allOk,
            'results'      => $this->results,
        ];
        file_put_contents($path . 'last-validation.json', json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));

        CLI::newLine();
        CLI::write("Selesai. Passed: {$this->passed}  Failed: {$this->failed}", $allOk ? 'green' : 'red');
        CLI::write('Laporan mesin: writable/staging/last-validation.json', 'cyan');
    }

    private function databaseReachable(): bool
    {
        try {
            Database::connect()->query('SELECT 1');

            return true;
        } catch (Throwable $e) {
            return false;
        }
    }

    private function record(string $endpoint, bool $ok, string $note): void
    {
        $this->results[] = ['endpoint' => $endpoint, 'ok' => $ok, 'note' => $note];
        if ($ok) {
            $this->passed++;
            CLI::write("[PASS] {$endpoint}: {$note}", 'green');
        } else {
            $this->failed++;
            CLI::write("[FAIL] {$endpoint}: {$note}", 'red');
        }
    }

    private function assertTrue(string $endpoint, bool $cond, string $okMsg, string $failMsg): void
    {
        $this->record($endpoint, $cond, $cond ? $okMsg : $failMsg);
    }

    private function assertLoginInvalid(MobileJsonService $svc): void
    {
        $r = $svc->login('__ci4_staging_nope__', 'wrongpass');
        $ok = ($r['status'] === 0 || $r['status'] === '0')
            && ($r['pesan'] ?? '') === 'Username atau Password tidak sesuai'
            && ! array_key_exists('token', $r);
        $this->assertTrue('login_invalid', $ok, 'status 0, pesan default, tanpa token', json_encode($r, JSON_UNESCAPED_UNICODE));
    }

    private function assertLoginEmpty(MobileJsonService $svc): void
    {
        $r = $svc->login('', '');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('token', $r);
        $this->assertTrue('login_empty', $ok, 'status 0 tanpa token', json_encode($r, JSON_UNESCAPED_UNICODE));
    }

    private function assertLoginWTokenEmpty(MobileJsonService $svc): void
    {
        $r = $svc->loginWToken('');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === ''
            && array_key_exists('status', $r) && array_key_exists('pesan', $r) && ! array_key_exists('token', $r);
        $this->assertTrue('login_w_token_empty', $ok, 'status 0, pesan kosong, tanpa token', json_encode($r));
    }

    private function assertLoginWTokenInvalid(MobileJsonService $svc): void
    {
        $r = $svc->loginWToken('invalid_token_ci4_staging_xyz');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '';
        $this->assertTrue('login_w_token_invalid', $ok, 'status 0', json_encode($r));
    }

    private function assertProfilInvalid(MobileJsonService $svc): void
    {
        $r = $svc->profil('');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('pegawai', $r);
        $this->assertTrue('profil_invalid_token', $ok, 'tanpa pegawai', json_encode($r));
    }

    private function assertPresensiTodayInvalid(MobileJsonService $svc): void
    {
        $r = $svc->presensiToday('');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
        $this->assertTrue('presensi_today_invalid', $ok, 'tanpa data', json_encode($r));
    }

    private function assertPresensiInvalid(MobileJsonService $svc): void
    {
        $r = $svc->presensi('');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
        $this->assertTrue('presensi_invalid', $ok, 'tanpa data', json_encode($r));
    }

    private function assertSaveMasukInvalidToken(MobileJsonService $svc): void
    {
        $r = $svc->saveMasuk('', 'p.png', '', '0', '0', '0');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === 'Tidak ada jadwal kerja';
        $this->assertTrue('save_masuk_invalid', $ok, 'pesan jadwal', json_encode($r));
    }

    private function assertSavePulangInvalidToken(MobileJsonService $svc): void
    {
        $r = $svc->savePulang('', 'p.png', '', '0', '0', '0');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === 'Tidak ada jadwal kerja';
        $this->assertTrue('save_pulang_invalid', $ok, 'pesan jadwal', json_encode($r));
    }

    private function assertSaveIstirahatInvalidToken(MobileJsonService $svc): void
    {
        $r = $svc->saveIstirahat('', '08:00', '');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === 'Tidak ada jadwal kerja';
        $this->assertTrue('save_istirahat_invalid', $ok, 'pesan jadwal', json_encode($r));
    }

    private function assertSaveAktifitasInvalidToken(MobileJsonService $svc): void
    {
        $r = $svc->saveAktifitas('', 'x.png', '', '2020-01-01', 'd');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '';
        $this->assertTrue('save_aktifitas_invalid', $ok, 'status 0 pesan kosong', json_encode($r));
    }

    private function assertSaveCutiInvalidToken(MobileJsonService $svc): void
    {
        $r = $svc->saveCuti('', 'x.png', '', '2020-01-01', 'a', 't');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '';
        $this->assertTrue('save_cuti_invalid', $ok, 'status 0 pesan kosong', json_encode($r));
    }

    private function assertBatalkanCutiInvalidToken(MobileJsonService $svc): void
    {
        $r = $svc->batalkanCuti('', '1');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '';
        $this->assertTrue('batalkan_cuti_invalid', $ok, 'status 0', json_encode($r));
    }

    private function assertBeritaInvalid(MobileJsonService $svc): void
    {
        $r = $svc->berita('', '0', '5');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
        $this->assertTrue('berita_invalid', $ok, 'tanpa data', json_encode($r));
    }

    private function assertCutiInvalid(MobileJsonService $svc): void
    {
        $r = $svc->cuti('', '0', '5');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
        $this->assertTrue('cuti_invalid', $ok, 'tanpa data', json_encode($r));
    }

    private function assertLemburInvalid(MobileJsonService $svc): void
    {
        $r = $svc->lembur('', '0', '5');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
        $this->assertTrue('lembur_invalid', $ok, 'tanpa data', json_encode($r));
    }

    private function assertLiburInvalid(MobileJsonService $svc): void
    {
        $r = $svc->libur('');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
        $this->assertTrue('libur_invalid', $ok, 'tanpa data', json_encode($r));
    }

    private function assertAktifitasInvalid(MobileJsonService $svc): void
    {
        $r = $svc->aktifitas('', '0', '5');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
        $this->assertTrue('aktifitas_invalid', $ok, 'tanpa data', json_encode($r));
    }

    private function assertDaftarTodayInvalid(MobileJsonService $svc): void
    {
        $r = $svc->daftarToday('');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ! array_key_exists('data', $r);
        $this->assertTrue('daftar_today_invalid', $ok, 'tanpa data', json_encode($r));
    }

    private function assertSavePpInvalidToken(MobileJsonService $svc): void
    {
        $r = $svc->savePp('', 'x.png', '');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === 'Tidak ada jadwal kerja';
        $this->assertTrue('save_pp_invalid', $ok, 'pesan sama CI3 untuk token salah', json_encode($r));
    }

    private function assertSavePasswordInvalidToken(MobileJsonService $svc): void
    {
        $r = $svc->savePassword('', 'a', 'b');
        $ok = ($r['status'] === 0 || $r['status'] === '0') && ($r['pesan'] ?? '') === '-';
        $this->assertTrue('save_password_invalid', $ok, 'pesan default -', json_encode($r));
    }

    private function fetchSampleToken($db): ?string
    {
        $row = $db->table('pegawai')->select('token')->where('token IS NOT NULL', null, false)->where('token !=', '')->limit(1)->get()->getRow();

        return $row && ! empty($row->token) ? (string) $row->token : null;
    }

    private function runAuthenticatedSuite(MobileJsonService $svc, string $token, bool $withUploads): void
    {
        $lw = $svc->loginWToken($token);
        $ok = ($lw['status'] === 1 || $lw['status'] === '1') && ($lw['pesan'] ?? '') === '' && ! array_key_exists('token', $lw);
        $this->assertTrue('login_w_token_valid', $ok, 'status 1, pesan kosong, tanpa token di JSON', json_encode($lw));

        $p = $svc->profil($token);
        $ok = ($p['status'] === 1 || $p['status'] === '1') && isset($p['pegawai']) && is_object($p['pegawai']);
        $this->assertTrue('profil_valid_token', $ok, 'status 1 + pegawai object', json_encode(['status' => $p['status'] ?? null]));

        $pt = $svc->presensiToday($token);
        $ok = ($pt['status'] === 1 || $pt['status'] === '1') && isset($pt['data']);
        $this->assertTrue('presensi_today_valid', $ok, 'status 1 + data', isset($pt['data']) ? 'data ok' : 'no data');

        $pr = $svc->presensi($token);
        $ok = ! isset($pr['data']) || (isset($pr['data']) && is_array($pr['data']));
        $ok = $ok && (($pr['status'] === 0 && ! isset($pr['data'])) || ($pr['status'] === 1 && isset($pr['data'])));
        $this->assertTrue('presensi_valid_token', (bool) $ok, 'status sesuai ada/tidak data', json_encode(['status' => $pr['status'] ?? null, 'has_data' => isset($pr['data'])]));

        $b = $svc->berita($token, '', '');
        $ok = ($b['status'] === 1 || $b['status'] === '1') && isset($b['data']) && is_array($b['data']);
        $this->assertTrue('berita_valid', $ok, 'status 1 + data array', json_encode(['count' => isset($b['data']) ? count($b['data']) : -1]));

        $c = $svc->cuti($token, '', '');
        $ok = ($c['status'] === 1 || $c['status'] === '1') && isset($c['data']) && is_array($c['data']);
        $this->assertTrue('cuti_valid', $ok, 'status 1 + data', '');

        $l = $svc->lembur($token, '', '');
        $ok = ($l['status'] === 1 || $l['status'] === '1') && isset($l['data']) && is_array($l['data']);
        $this->assertTrue('lembur_valid', $ok, 'status 1 + data', '');

        $lib = $svc->libur($token);
        $ok = ($lib['status'] === 1 || $lib['status'] === '1') && isset($lib['data']) && is_array($lib['data']);
        $this->assertTrue('libur_valid', $ok, 'status 1 + data', '');

        $a = $svc->aktifitas($token, '', '');
        $ok = ($a['status'] === 1 || $a['status'] === '1') && isset($a['data']) && is_array($a['data']);
        $this->assertTrue('aktifitas_valid', $ok, 'status 1 + data', '');

        $d = $svc->daftarToday($token);
        $ok = ($d['status'] === 0 && ! isset($d['data'])) || ($d['status'] === 1 && isset($d['data']) && is_array($d['data']));
        $this->assertTrue('daftar_today_valid', $ok, 'status konsisten', json_encode(['status' => $d['status'] ?? null]));

        // save_istirahat: mulai & selesai kosong — CI4 stabil (tanpa UPDATE)
        $ist = $svc->saveIstirahat($token, '', '');
        $ok  = ($ist['status'] === 0 || $ist['status'] === '0') && ($ist['pesan'] ?? '') === 'Tidak ada jadwal kerja';
        $this->assertTrue('save_istirahat_empty_both_ci4', $ok, 'deviasi terdokumen vs CI3 undefined', json_encode($ist));

        $envPass = env('STAGING_VALIDATE_PASS', '');
        if (is_string($envPass) && $envPass !== '') {
            $sp = $svc->savePassword($token, 'definitely_wrong_old_password_xx', 'new');
            $ok = ($sp['status'] === 0 || $sp['status'] === '0')
                && str_contains((string) ($sp['pesan'] ?? ''), 'Password lama tidak sesuai');
            $this->assertTrue('save_password_wrong_old', $ok, 'pesan salah password lama', json_encode($sp));
        }

        if ($withUploads) {
            $this->runTinyUploadPp($svc, $token);
        }
    }

    private function runTinyUploadPp(MobileJsonService $svc, string $token): void
    {
        // 1x1 GIF transparan (sangat kecil)
        $b64   = 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
        $db    = Database::connect();
        $row   = $db->table('pegawai')->select('id_pegawai, photo')->where('token', $token)->get()->getRow();
        $oldPh = $row->photo ?? '';

        $r = $svc->savePp($token, 'staging.gif', $b64);
        $dir = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'pengguna';
        $ok  = ($r['status'] === 1 || $r['status'] === '1');
        if ($ok && $row) {
            $newRow = $db->table('pegawai')->select('photo')->where('id_pegawai', $row->id_pegawai)->get()->getRow();
            $fn     = $newRow->photo ?? '';
            $path   = $dir . DIRECTORY_SEPARATOR . $fn;
            $ok     = $ok && $fn !== '' && is_file($path);
            if (is_file($path)) {
                @unlink($path);
            }
            $db->table('pegawai')->where('id_pegawai', $row->id_pegawai)->update(['photo' => $oldPh]);
        }
        $this->assertTrue('save_pp_upload_roundtrip', $ok, 'upload + file ada + revert photo DB', json_encode($r));
    }

    private function assertUploadDirectories(): void
    {
        $subs = ['dokcuti', 'aktifitas', 'absen' . DIRECTORY_SEPARATOR . 'masuk', 'absen' . DIRECTORY_SEPARATOR . 'pulang', 'pengguna'];
        $ok   = true;
        foreach ($subs as $sub) {
            $p = FCPATH . 'assets' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . $sub;
            if (! is_dir($p)) {
                mkdir($p, 0755, true);
            }
            $ok = $ok && is_dir($p) && is_writable($p);
        }
        $this->assertTrue('upload_directories', $ok, 'folder upload ada & writable', 'cek permission');
    }
}
