<?php
namespace App\Controllers;

use App\Core\Audit;
use App\Core\Auth;
use App\Core\Controller;
use App\Core\Database;
use App\Core\Dates;
use App\Core\Flash;
use App\Core\Num;
use App\Core\Request;
use App\Core\Response;
use App\Core\Settings;
use App\Services\ApprovalService;
use App\Services\ExportService;
use App\Services\RevisionService;

/** Modul Revisi Invoice (RF). */
class RevisionController extends Controller
{
    protected string $module = 'revisi';

    private function filters(): array
    {
        $f = [];
        foreach (['cari', 'status', 'action'] as $k) { $v = trim((string)Request::get($k, '')); if ($v !== '') $f[$k] = $v; }
        $v = Request::int('revision_type_id'); if ($v > 0) $f['revision_type_id'] = $v;
        foreach (['dari', 'sampai'] as $k) { $d = Request::date($k); if ($d) $f[$k] = $d; }
        return $f;
    }

    public function index(): void
    {
        $f = $this->filters();
        [$page, $per] = $this->pageParams();
        $this->view('revisi/index', [
            'judul' => 'Revisi Invoice (RF)',
            'subjudul' => 'Register revisi, penggantian, pembatalan, dan penghapusan invoice',
            'p' => RevisionService::listing($f, (string)Request::get('urut', 'rf_date'), (string)Request::get('arah', 'desc'), $page, $per),
            'stat' => RevisionService::statistics(), 'filters' => $f,
            'gaps' => RevisionService::numberingGaps(),
            'types' => Database::i()->all('SELECT * FROM revision_types WHERE deleted_at IS NULL ORDER BY sort_order'),
            'actions' => RevisionService::ACTIONS,
        ]);
    }

    public function show(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $db = Database::i();
        $rev = $db->one(
            'SELECT r.*, rt.name AS jenis_revisi, rt.default_action,
                    u.name AS requested_by_name, ua.name AS approved_by_name
               FROM invoice_revisions r
               LEFT JOIN revision_types rt ON rt.id = r.revision_type_id
               LEFT JOIN users u ON u.id = r.requested_by
               LEFT JOIN users ua ON ua.id = r.approved_by
              WHERE r.id = :id', ['id' => $id]
        );
        if (!$rev) Response::abort(404, 'Data revisi tidak ditemukan.');
        $m = new \App\Models\Invoice();
        $this->view('revisi/detail', [
            'judul' => 'Revisi ' . $rev['rf_no'],
            'subjudul' => ($rev['jenis_revisi'] ?: 'Revisi Invoice') . ' · ' . tgl($rev['rf_date']),
            'rev' => $rev,
            'old' => $rev['old_invoice_id'] ? $m->findFull((int)$rev['old_invoice_id']) : null,
            'new' => $rev['new_invoice_id'] ? $m->findFull((int)$rev['new_invoice_id']) : null,
            'audit' => $db->all('SELECT * FROM audit_logs WHERE entity_type="invoice_revision" AND entity_id=:id ORDER BY created_at DESC', ['id' => $id]),
        ]);
    }

    public function create(): void
    {
        $this->view('revisi/form', array_merge($this->formData(), [
            'judul' => 'Tambah Revisi Invoice (RF)', 'rev' => null,
            'nomorSaran' => RevisionService::nextRfNo(),
        ]));
    }

    public function edit(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $rev = Database::i()->one('SELECT * FROM invoice_revisions WHERE id = :id AND deleted_at IS NULL', ['id' => $id]);
        if (!$rev) Response::abort(404, 'Data revisi tidak ditemukan.');
        if ($rev['status'] === 'approved') {
            Flash::warning('Revisi yang sudah disetujui tidak dapat diubah. Batalkan penerapan terlebih dahulu.');
            $this->redirect('/revisi/' . $id);
        }
        $this->view('revisi/form', array_merge($this->formData(), ['judul' => 'Ubah Revisi ' . $rev['rf_no'], 'rev' => $rev]));
    }

    private function formData(): array
    {
        return [
            'types' => Database::i()->all('SELECT * FROM revision_types WHERE deleted_at IS NULL AND is_active=1 ORDER BY sort_order'),
            'actions' => RevisionService::ACTIONS,
        ];
    }

    private function payload(): array
    {
        return [
            'rf_no' => \App\Services\MasterResolver::key((string)Request::post('rf_no', '')),
            'rf_date' => Request::date('rf_date'),
            'old_invoice_no' => \App\Services\MasterResolver::key((string)Request::post('old_invoice_no', '')) ?: null,
            'new_invoice_no' => \App\Services\MasterResolver::key((string)Request::post('new_invoice_no', '')) ?: null,
            'old_invoice_date' => Request::date('old_invoice_date'),
            'original_amount' => Request::money('original_amount'),
            'revision_type_id' => Request::int('revision_type_id') ?: null,
            'old_invoice_action' => in_array(Request::post('old_invoice_action'), array_keys(RevisionService::ACTIONS), true)
                                    ? (string)Request::post('old_invoice_action') : 'replaced',
            'reason' => mb_substr((string)Request::post('reason', ''), 0, 2000) ?: null,
        ];
    }

    public function store(): void
    {
        $this->validate([
            'rf_no' => 'required|maxlen:40',
            'old_invoice_no' => 'required|maxlen:60',
        ], ['rf_no' => 'Nomor RF', 'old_invoice_no' => 'Nomor invoice lama'], '/revisi/tambah');

        $db = Database::i();
        $d = $this->payload();
        if ($db->value('SELECT COUNT(*) FROM invoice_revisions WHERE rf_no = ? AND deleted_at IS NULL', [$d['rf_no']])) {
            Flash::error('Nomor RF ' . $d['rf_no'] . ' sudah digunakan.');
            $this->redirect('/revisi/tambah');
        }
        $old = $db->one('SELECT id, invoice_date, gross_amount FROM invoices WHERE invoice_no = ? AND deleted_at IS NULL', [$d['old_invoice_no']]);
        $new = $d['new_invoice_no'] ? $db->one('SELECT id, invoice_date, gross_amount FROM invoices WHERE invoice_no = ? AND deleted_at IS NULL', [$d['new_invoice_no']]) : null;

        $id = $db->insert('invoice_revisions', array_merge($d, [
            'old_invoice_id' => $old['id'] ?? null,
            'new_invoice_id' => $new['id'] ?? null,
            'old_invoice_date' => $d['old_invoice_date'] ?: ($old['invoice_date'] ?? null),
            'new_invoice_date' => $new['invoice_date'] ?? null,
            'original_amount' => $d['original_amount'] ?: (float)($old['gross_amount'] ?? 0),
            'revised_amount' => (float)($new['gross_amount'] ?? 0),
            'status' => $old ? 'pending' : 'menunggu_pencocokan',
            'match_notes' => $old ? null : 'Invoice lama ' . $d['old_invoice_no'] . ' belum ada di database.',
            'requested_by' => Auth::id() ?: null,
            'source' => 'manual',
        ]));
        Audit::log('revisi', 'create', 'invoice_revision', $id, 'Revisi ' . $d['rf_no'] . ' dibuat', null, $d);
        Flash::success('Revisi ' . $d['rf_no'] . ' berhasil disimpan dan menunggu persetujuan.');
        $this->redirect('/revisi/' . $id);
    }

    public function update(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $db = Database::i();
        $before = $db->one('SELECT * FROM invoice_revisions WHERE id = :id', ['id' => $id]);
        if (!$before) Response::abort(404, 'Data revisi tidak ditemukan.');
        $d = $this->payload();
        $old = $d['old_invoice_no'] ? $db->one('SELECT id FROM invoices WHERE invoice_no = ? AND deleted_at IS NULL', [$d['old_invoice_no']]) : null;
        $new = $d['new_invoice_no'] ? $db->one('SELECT id FROM invoices WHERE invoice_no = ? AND deleted_at IS NULL', [$d['new_invoice_no']]) : null;
        $d['old_invoice_id'] = $old['id'] ?? null;
        $d['new_invoice_id'] = $new['id'] ?? null;
        $d['status'] = $old ? 'pending' : 'menunggu_pencocokan';
        $db->update('invoice_revisions', $d, 'id = :pk', ['pk' => $id]);
        Audit::logDiff('revisi', 'update', 'invoice_revision', $id, $before, $d, 'Revisi diubah');
        Flash::success('Revisi berhasil diperbarui.');
        $this->redirect('/revisi/' . $id);
    }

    public function approve(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        try {
            $res = RevisionService::apply($id);
            if (!empty($res['sukses'])) {
                Flash::success($res['pesan'] . ' Invoice lama dikeluarkan dari piutang aktif; pendapatan tidak dihitung dua kali ('
                    . rp($res['revenue_dicegah'] ?? 0) . ' dicegah).');
            } else {
                Flash::warning($res['pesan']);
            }
        } catch (\Throwable $e) {
            Flash::error('Penerapan revisi gagal: ' . $e->getMessage());
        }
        $this->redirect('/revisi/' . $id);
    }

    public function reject(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $alasan = trim((string)Request::post('alasan', ''));
        $rev = Database::i()->one('SELECT * FROM invoice_revisions WHERE id = :id', ['id' => $id]);
        if (!$rev) Response::abort(404, 'Data revisi tidak ditemukan.');
        try {
            if (!empty($rev['applied_at'])) {
                RevisionService::unapply($id, $alasan);
                Flash::success('Penerapan revisi dibatalkan dan invoice lama diaktifkan kembali.');
            } else {
                Database::i()->update('invoice_revisions', ['status' => 'rejected',
                    'match_notes' => mb_substr('Ditolak: ' . $alasan, 0, 400)], 'id = :pk', ['pk' => $id]);
                Audit::log('revisi', 'reject', 'invoice_revision', $id, 'Revisi ditolak. ' . $alasan);
                Flash::success('Revisi ditolak.');
            }
        } catch (\Throwable $e) { Flash::error($e->getMessage()); }
        $this->redirect('/revisi/' . $id);
    }

    public function destroy(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $rev = Database::i()->one('SELECT * FROM invoice_revisions WHERE id = :id', ['id' => $id]);
        if (!$rev) Response::abort(404, 'Data revisi tidak ditemukan.');
        if (!empty($rev['applied_at'])) {
            Flash::error('Revisi yang sudah diterapkan tidak dapat dihapus. Batalkan penerapannya terlebih dahulu.');
            $this->redirect('/revisi/' . $id);
        }
        Database::i()->exec('UPDATE invoice_revisions SET deleted_at = NOW() WHERE id = ?', [$id]);
        Audit::log('revisi', 'delete', 'invoice_revision', $id, 'Revisi ' . $rev['rf_no'] . ' dihapus', $rev, null);
        Flash::success('Revisi dihapus.');
        $this->redirect('/revisi');
    }

    public function rematch(): void
    {
        $n = RevisionService::rematchPending();
        Flash::success("$n revisi berhasil dicocokkan ulang dengan invoice di database.");
        $this->redirect('/revisi');
    }

    public function export(): void
    {
        $f = $this->filters();
        $p = RevisionService::listing($f, 'rf_date', 'desc', 1, Settings::int('laporan.maks_baris_export', 50000));
        ExportService::send((string)Request::get('format', 'xlsx'), [
            'judul' => 'LAPORAN REVISI INVOICE (RF)',
            'tanggal_lapor' => Dates::fmt(Dates::today()),
            'nama_berkas' => 'laporan_revisi_invoice', 'sheet' => 'Revisi Invoice', 'orientasi' => 'landscape',
        ], [
            ['label' => 'Nomor RF',        'key' => 'rf_no',            'type' => 'text'],
            ['label' => 'Tanggal RF',      'key' => 'rf_date',          'type' => 'date'],
            ['label' => 'Invoice Lama',    'key' => 'old_invoice_no',   'type' => 'text'],
            ['label' => 'Tgl Invoice Lama','key' => 'old_invoice_date', 'type' => 'date'],
            ['label' => 'Invoice Baru',    'key' => 'new_invoice_no',   'type' => 'text'],
            ['label' => 'Customer',        'key' => 'customer_name',    'type' => 'text'],
            ['label' => 'Jenis Revisi',    'key' => 'jenis_revisi',     'type' => 'text'],
            ['label' => 'Tindakan',        'key' => 'old_invoice_action','type' => 'text'],
            ['label' => 'Nominal Lama',    'key' => 'original_amount',  'type' => 'money'],
            ['label' => 'Nominal Baru',    'key' => 'revised_amount',   'type' => 'money'],
            ['label' => 'Status',          'key' => 'status',           'type' => 'text'],
            ['label' => 'Alasan',          'key' => 'reason',           'type' => 'text', 'width' => 60, 'pdf_width' => 3],
        ], $p['data'], [
            'rf_no' => 'TOTAL',
            'original_amount' => array_sum(array_map(fn($r) => (float)$r['original_amount'], $p['data'])),
            'revised_amount' => array_sum(array_map(fn($r) => (float)$r['revised_amount'], $p['data'])),
        ]);
    }
}
