<?php
namespace App\Services;

use App\Core\Acl;
use App\Core\Audit;
use App\Core\Auth;
use App\Core\Database;
use App\Core\Dates;
use App\Core\Num;

/** Alur persetujuan untuk transaksi finansial sensitif. */
class ApprovalService
{
    public static function flows(): array
    {
        return Database::i()->all('SELECT * FROM approval_flows WHERE is_active = 1 ORDER BY id');
    }

    /** Membuat permintaan persetujuan. */
    public static function request(string $module, string $action, string $entityType, $entityId, string $title, float $amount = 0, array $payload = []): int
    {
        $db = Database::i();
        $flow = $db->one('SELECT * FROM approval_flows WHERE module = :m AND action = :a AND is_active = 1 LIMIT 1',
                         ['m' => $module, 'a' => $action]);
        $no = 'APV/' . date('ymd') . '/' . strtoupper(bin2hex(random_bytes(3)));
        $id = $db->insert('approval_requests', [
            'request_no'  => $no,
            'flow_id'     => $flow['id'] ?? null,
            'module'      => $module,
            'entity_type' => $entityType,
            'entity_id'   => $entityId !== null ? (int)$entityId : null,
            'action'      => $action,
            'title'       => mb_substr($title, 0, 255),
            'amount'      => $amount,
            'payload_json'=> $payload ? json_encode($payload, JSON_UNESCAPED_UNICODE) : null,
            'status'      => 'pending',
            'current_level' => 1,
            'total_level' => (int)($flow['level_count'] ?? 1),
            'requested_by'=> Auth::id() ?: null,
        ]);
        $db->insert('approval_histories', [
            'approval_request_id' => $id, 'level' => 1,
            'actor_id' => Auth::id() ?: null, 'actor_name' => Auth::check() ? Auth::name() : 'Sistem',
            'decision' => 'submitted', 'notes' => $title, 'ip_address' => \App\Core\Request::ip(),
        ]);
        Audit::log($module, 'request_approval', $entityType, $entityId, 'Permintaan persetujuan: ' . $title, null, null, $id);
        return $id;
    }

    /** Memutuskan (setujui/tolak) sebuah permintaan. */
    public static function decide(int $requestId, string $decision, string $notes = ''): array
    {
        $db = Database::i();
        if (!in_array($decision, ['approved', 'rejected', 'cancelled'], true)) {
            throw new \RuntimeException('Keputusan tidak valid.');
        }
        return $db->transaction(function () use ($db, $requestId, $decision, $notes) {
            $r = $db->one('SELECT * FROM approval_requests WHERE id = :id', ['id' => $requestId]);
            if (!$r) throw new \RuntimeException('Permintaan persetujuan tidak ditemukan.');
            if ($r['status'] !== 'pending') throw new \RuntimeException('Permintaan ini sudah diputuskan sebelumnya.');

            $level = (int)$r['current_level'];
            $db->insert('approval_histories', [
                'approval_request_id' => $requestId, 'level' => $level,
                'actor_id' => Auth::id() ?: null, 'actor_name' => Auth::check() ? Auth::name() : 'Sistem',
                'decision' => $decision, 'notes' => mb_substr($notes, 0, 1000), 'ip_address' => \App\Core\Request::ip(),
            ]);

            $final = true;
            if ($decision === 'approved' && $level < (int)$r['total_level']) {
                $db->update('approval_requests', ['current_level' => $level + 1], 'id = :pk', ['pk' => $requestId]);
                $final = false;
            }
            if ($final) {
                $db->update('approval_requests', [
                    'status' => $decision, 'decided_by' => Auth::id() ?: null, 'decided_at' => Dates::now(),
                    'notes' => mb_substr($notes, 0, 1000),
                ], 'id = :pk', ['pk' => $requestId]);
                if ($decision === 'approved') self::executeApproved($r);
                elseif ($decision === 'rejected') self::executeRejected($r);
            }

            Audit::log($r['module'], $decision === 'approved' ? 'approve' : 'reject', $r['entity_type'], $r['entity_id'],
                'Persetujuan ' . $r['request_no'] . ': ' . $decision . ($notes ? ' — ' . $notes : ''), null, null, $requestId);

            return ['sukses' => true, 'final' => $final,
                    'pesan' => $final
                        ? ($decision === 'approved' ? 'Permintaan disetujui dan tindakan telah dijalankan.' : 'Permintaan ditolak.')
                        : 'Persetujuan tingkat ' . $level . ' selesai. Menunggu tingkat berikutnya.'];
        });
    }

    /** Menjalankan efek dari permintaan yang disetujui. */
    private static function executeApproved(array $r): void
    {
        $db = Database::i();
        $id = (int)$r['entity_id'];
        switch ($r['module'] . '.' . $r['action']) {
            case 'pembayaran.adjust':
            case 'pembayaran.refund':
                $db->update('payments', ['status' => 'posted', 'approval_status' => 'approved',
                    'approved_by' => Auth::id() ?: null, 'approved_at' => Dates::now()], 'id = :pk', ['pk' => $id]);
                foreach ($db->column('SELECT invoice_id FROM payment_allocations WHERE payment_id = ? AND deleted_at IS NULL', [$id]) as $iid) {
                    ArCalculator::recalculate((int)$iid);
                }
                break;

            case 'pembayaran.reversal':
                PaymentService::reverse($id, 'Disetujui melalui permintaan ' . $r['request_no']);
                break;

            case 'pembayaran.delete':
                PaymentService::delete($id, 'Disetujui melalui permintaan ' . $r['request_no']);
                break;

            case 'invoice.cancel':
                $db->update('invoices', ['doc_status' => 'cancelled', 'is_revenue_counted' => 0], 'id = :pk', ['pk' => $id]);
                ArCalculator::recalculate($id);
                break;

            case 'revisi.approve':
                $db->update('invoice_revisions', ['status' => 'approved', 'approved_by' => Auth::id() ?: null,
                    'approved_at' => Dates::now()], 'id = :pk', ['pk' => $id]);
                RevisionService::apply($id);
                break;

            case 'nota_kredit.approve':
            case 'nota_kredit.manual_adjust':
                $cn = $db->one('SELECT * FROM credit_notes WHERE id = :id', ['id' => $id]);
                $db->update('credit_notes', ['status' => 'approved', 'approved_by' => Auth::id() ?: null,
                    'approved_at' => Dates::now()], 'id = :pk', ['pk' => $id]);
                if ($cn) ArCalculator::recalculate((int)$cn['invoice_id']);
                break;

            case 'hapus_buku.approve':
                $cn2 = $db->one('SELECT * FROM credit_notes WHERE id = :id', ['id' => $id]);
                $db->update('credit_notes', ['status' => 'approved', 'approved_by' => Auth::id() ?: null,
                    'approved_at' => Dates::now()], 'id = :pk', ['pk' => $id]);
                if ($cn2) {
                    $db->update('invoices', ['doc_status' => 'written_off', 'is_revenue_counted' => 0],
                                'id = :pk', ['pk' => (int)$cn2['invoice_id']]);
                    ArCalculator::recalculate((int)$cn2['invoice_id']);
                }
                break;

            case 'import.rollback':
                ImportService::rollback($id);
                break;
        }
    }

    private static function executeRejected(array $r): void
    {
        $db = Database::i();
        $id = (int)$r['entity_id'];
        switch ($r['module']) {
            case 'pembayaran':
                $db->update('payments', ['approval_status' => 'rejected', 'status' => 'draft'], 'id = :pk', ['pk' => $id]);
                break;
            case 'revisi':
                $db->update('invoice_revisions', ['status' => 'rejected'], 'id = :pk', ['pk' => $id]);
                break;
            case 'nota_kredit':
            case 'hapus_buku':
                $db->update('credit_notes', ['status' => 'rejected'], 'id = :pk', ['pk' => $id]);
                break;
        }
    }

    /** Daftar permintaan persetujuan. */
    public static function listing(array $f = [], int $page = 1, int $perPage = 50): array
    {
        $db = Database::i();
        $w = ['1=1'];
        $p = [];
        if (!empty($f['status'])) { $w[] = 'ar.status = :st'; $p['st'] = $f['status']; }
        else                      { $w[] = "ar.status = 'pending'"; }
        if (!empty($f['module'])) { $w[] = 'ar.module = :m'; $p['m'] = $f['module']; }
        if (!empty($f['cari']))   { $w[] = '(ar.request_no LIKE :q OR ar.title LIKE :q)'; $p['q'] = '%' . $f['cari'] . '%'; }
        if (!empty($f['dari']))   { $w[] = 'DATE(ar.created_at) >= :d1'; $p['d1'] = $f['dari']; }
        if (!empty($f['sampai'])) { $w[] = 'DATE(ar.created_at) <= :d2'; $p['d2'] = $f['sampai']; }
        $where = implode(' AND ', $w);

        $sql = 'SELECT ar.*, af.name AS flow_name, u.name AS requested_by_name, ud.name AS decided_by_name
                  FROM approval_requests ar
                  LEFT JOIN approval_flows af ON af.id = ar.flow_id
                  LEFT JOIN users u ON u.id = ar.requested_by
                  LEFT JOIN users ud ON ud.id = ar.decided_by
                 WHERE ' . $where . ' ORDER BY ar.created_at DESC, ar.id DESC';
        $m = new \App\Models\ApprovalRequest();
        return $m->paginate($sql, $p, $page, $perPage, 'SELECT COUNT(*) FROM approval_requests ar WHERE ' . $where);
    }

    public static function pendingCount(): int
    {
        if (!Acl::can('persetujuan', 'view')) return 0;
        try { return (int)Database::i()->value("SELECT COUNT(*) FROM approval_requests WHERE status = 'pending'"); }
        catch (\Throwable $e) { return 0; }
    }

    public static function history(int $requestId): array
    {
        return Database::i()->all(
            'SELECT h.*, u.name AS user_name FROM approval_histories h
               LEFT JOIN users u ON u.id = h.actor_id
              WHERE h.approval_request_id = :id ORDER BY h.id',
            ['id' => $requestId]
        );
    }

    public static function find(int $id): ?array
    {
        return Database::i()->one(
            'SELECT ar.*, af.name AS flow_name, u.name AS requested_by_name, ud.name AS decided_by_name
               FROM approval_requests ar
               LEFT JOIN approval_flows af ON af.id = ar.flow_id
               LEFT JOIN users u ON u.id = ar.requested_by
               LEFT JOIN users ud ON ud.id = ar.decided_by
              WHERE ar.id = :id',
            ['id' => $id]
        );
    }
}
