<?php
namespace App\Controllers;

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\Models\Payment;
use App\Services\ExportService;
use App\Services\PaymentService;

/** Modul pembayaran multi-alokasi. */
class PaymentController extends Controller
{
    protected string $module = 'pembayaran';

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

    public function index(): void
    {
        $f = $this->filters();
        [$page, $per] = $this->pageParams();
        $m = new Payment();
        $this->view('pembayaran/index', [
            'judul' => 'Pembayaran',
            'subjudul' => 'Seluruh transaksi penerimaan pembayaran',
            'p' => $m->listing($f, (string)Request::get('urut', 'payment_date'), (string)Request::get('arah', 'desc'), $page, $per),
            'tot' => $m->listingTotals($f), 'filters' => $f,
            'banks' => Database::i()->all('SELECT id,name FROM banks WHERE deleted_at IS NULL AND is_active=1 ORDER BY name'),
            'methods' => PaymentService::METHODS, 'types' => PaymentService::TYPES,
        ]);
    }

    public function show(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $m = new Payment();
        $p = $m->findFull($id);
        if (!$p) Response::abort(404, 'Data pembayaran tidak ditemukan.');
        $this->view('pembayaran/detail', [
            'judul' => 'Pembayaran ' . $p['payment_no'],
            'subjudul' => tgl($p['payment_date']) . ' · ' . ($p['customer_name'] ?: '-'),
            'pay' => $p, 'allocations' => $m->allocations($id),
            'reversal' => $p['reversal_of_payment_id'] ? $m->find((int)$p['reversal_of_payment_id']) : null,
            'audit' => Database::i()->all('SELECT * FROM audit_logs WHERE entity_type="payment" AND entity_id=:id ORDER BY created_at DESC LIMIT 40', ['id' => $id]),
        ]);
    }

    public function create(): void
    {
        $invoiceId = Request::int('invoice_id');
        $prefill = null;
        if ($invoiceId > 0) {
            $prefill = Database::i()->one(
                'SELECT i.*, c.name AS customer_name FROM invoices i LEFT JOIN customers c ON c.id=i.customer_id WHERE i.id = ?',
                [$invoiceId]
            );
        }
        $this->view('pembayaran/form', array_merge($this->formData(), [
            'judul' => 'Catat Pembayaran', 'pay' => null, 'allocations' => [],
            'prefill' => $prefill, 'nomorSaran' => (new Payment())->nextNo(),
        ]));
    }

    public function edit(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $m = new Payment();
        $pay = $m->findFull($id);
        if (!$pay) Response::abort(404, 'Data pembayaran tidak ditemukan.');
        if ($pay['status'] === 'reversed') {
            Flash::warning('Pembayaran yang sudah dibalik tidak dapat diubah.');
            $this->redirect('/pembayaran/' . $id);
        }
        $this->view('pembayaran/form', array_merge($this->formData(), [
            'judul' => 'Ubah Pembayaran ' . $pay['payment_no'],
            'pay' => $pay, 'allocations' => $m->allocations($id), 'prefill' => null,
        ]));
    }

    private function formData(): array
    {
        return [
            'banks' => Database::i()->all('SELECT id,name,account_no FROM banks WHERE deleted_at IS NULL AND is_active=1 ORDER BY name'),
            'customers' => Database::i()->all('SELECT id,code,name FROM customers WHERE deleted_at IS NULL ORDER BY name LIMIT 3000'),
            'methods' => PaymentService::METHODS,
            'types' => PaymentService::TYPES,
        ];
    }

    private function readAllocations(): array
    {
        $ids = Request::array('alokasi_invoice_id');
        $amts = Request::array('alokasi_nilai');
        $notes = Request::array('alokasi_catatan');
        $out = [];
        foreach ($ids as $i => $iid) {
            $iid = (int)$iid;
            $amt = Num::parse((string)($amts[$i] ?? 0));
            if ($iid <= 0 || abs($amt) < 0.005) continue;
            $out[] = ['invoice_id' => $iid, 'amount' => $amt, 'notes' => mb_substr((string)($notes[$i] ?? ''), 0, 255) ?: null];
        }
        return $out;
    }

    public function store(): void
    {
        $this->validate(['payment_date' => 'required|date'], ['payment_date' => 'Tanggal pembayaran'], '/pembayaran/tambah');
        $alloc = $this->readAllocations();
        if (!$alloc) { Flash::error('Tambahkan minimal satu invoice pada alokasi pembayaran.'); $this->redirect('/pembayaran/tambah'); }
        try {
            $res = PaymentService::create([
                'payment_date' => Request::date('payment_date'),
                'customer_id' => Request::int('customer_id') ?: null,
                'bank_id' => Request::int('bank_id') ?: null,
                'method' => (string)Request::post('method', 'TRANSFER'),
                'reference_no' => mb_substr((string)Request::post('reference_no', ''), 0, 80) ?: null,
                'payment_type' => (string)Request::post('payment_type', 'PELUNASAN'),
                'amount' => Request::money('amount'),
                'notes' => mb_substr((string)Request::post('notes', ''), 0, 2000) ?: null,
            ], $alloc);
            Flash::success($res['pesan']);
            $this->redirect('/pembayaran/' . $res['payment_id']);
        } catch (\Throwable $e) {
            Flash::error($e->getMessage());
            $this->redirect('/pembayaran/tambah');
        }
    }

    public function update(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $alloc = $this->readAllocations();
        try {
            PaymentService::update($id, [
                'payment_date' => Request::date('payment_date'),
                'customer_id' => Request::int('customer_id') ?: null,
                'bank_id' => Request::int('bank_id') ?: null,
                'method' => (string)Request::post('method', 'TRANSFER'),
                'reference_no' => mb_substr((string)Request::post('reference_no', ''), 0, 80) ?: null,
                'payment_type' => (string)Request::post('payment_type', 'PELUNASAN'),
                'amount' => Request::money('amount'),
                'notes' => mb_substr((string)Request::post('notes', ''), 0, 2000) ?: null,
            ], $alloc);
            Flash::success('Pembayaran berhasil diperbarui dan saldo invoice dihitung ulang.');
        } catch (\Throwable $e) {
            Flash::error($e->getMessage());
        }
        $this->redirect('/pembayaran/' . $id);
    }

    public function reverse(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $alasan = trim((string)Request::post('alasan', ''));
        if ($alasan === '') { Flash::error('Alasan pembalikan wajib diisi.'); $this->redirect('/pembayaran/' . $id); }
        try {
            $res = PaymentService::reverse($id, $alasan);
            Flash::success($res['pesan'] . ' Transaksi pembalikan telah dibuat sebagai catatan terpisah.');
        } catch (\Throwable $e) { Flash::error($e->getMessage()); }
        $this->redirect('/pembayaran/' . $id);
    }

    public function destroy(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $alasan = trim((string)Request::post('alasan', 'Koreksi input'));
        try {
            PaymentService::delete($id, $alasan);
            Flash::success('Pembayaran dihapus dan saldo invoice dihitung ulang.');
        } catch (\Throwable $e) { Flash::error($e->getMessage()); }
        $this->redirect('/pembayaran');
    }

    public function approve(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        try {
            $res = PaymentService::approve($id);
            Flash::success($res['pesan']);
        } catch (\Throwable $e) { Flash::error($e->getMessage()); }
        $this->redirect('/pembayaran/' . $id);
    }

    /** Saran alokasi FIFO (JSON). */
    public function suggest(): void
    {
        $cust = Request::int('customer_id');
        $amount = Request::money('amount');
        if ($cust <= 0) $this->json(['sukses' => false, 'pesan' => 'Pilih customer terlebih dahulu.']);
        $this->json(['sukses' => true] + PaymentService::suggestAllocation($cust, $amount));
    }

    public function print(array $params): void
    {
        $id = (int)($params['id'] ?? 0);
        $m = new Payment();
        $pay = $m->findFull($id);
        if (!$pay) Response::abort(404, 'Data pembayaran tidak ditemukan.');
        \App\Core\View::render('pembayaran/cetak', ['pay' => $pay, 'allocations' => $m->allocations($id)], '');
    }

    public function export(): void
    {
        $f = $this->filters();
        $m = new Payment();
        $p = $m->listing($f, 'payment_date', 'desc', 1, Settings::int('laporan.maks_baris_export', 50000));
        $tot = $m->listingTotals($f);

        $rows = Database::i()->all(
            'SELECT p.payment_no, p.payment_date, p.method, p.payment_type, p.reference_no, p.status,
                    b.name AS bank_name, c.name AS customer_name,
                    i.invoice_no, i.invoice_date, i.due_date, i.net_amount, i.outstanding_amount,
                    pa.amount AS alokasi,
                    DATEDIFF(p.payment_date, COALESCE(i.sent_date, i.invoice_date)) AS hari_penagihan,
                    GREATEST(0, DATEDIFF(p.payment_date, i.due_date)) AS hari_terlambat
               FROM payment_allocations pa
               JOIN payments p ON p.id = pa.payment_id
               JOIN invoices i ON i.id = pa.invoice_id
               LEFT JOIN banks b ON b.id = p.bank_id
               LEFT JOIN customers c ON c.id = i.customer_id
              WHERE pa.deleted_at IS NULL AND p.deleted_at IS NULL'
            . (!empty($f['dari']) ? ' AND p.payment_date >= :d1' : '')
            . (!empty($f['sampai']) ? ' AND p.payment_date <= :d2' : '')
            . (!empty($f['customer_id']) ? ' AND i.customer_id = :c' : '')
            . ' ORDER BY p.payment_date DESC, p.id DESC LIMIT ' . Settings::int('laporan.maks_baris_export', 50000),
            array_filter(['d1' => $f['dari'] ?? null, 'd2' => $f['sampai'] ?? null, 'c' => $f['customer_id'] ?? null], fn($v) => $v !== null)
        );

        ExportService::send((string)Request::get('format', 'xlsx'), [
            'judul' => 'RIWAYAT PEMBAYARAN',
            'periode' => (!empty($f['dari']) || !empty($f['sampai']))
                ? Dates::fmt($f['dari'] ?? null) . ' s/d ' . Dates::fmt($f['sampai'] ?? null) : 'Seluruh periode',
            'nama_berkas' => 'riwayat_pembayaran', 'sheet' => 'Pembayaran', 'orientasi' => 'landscape',
        ], [
            ['label' => 'No. Pembayaran', 'key' => 'payment_no',   'type' => 'text'],
            ['label' => 'Tgl Bayar',      'key' => 'payment_date',  'type' => 'date'],
            ['label' => 'Customer',       'key' => 'customer_name', 'type' => 'text'],
            ['label' => 'No. Invoice',    'key' => 'invoice_no',    'type' => 'text'],
            ['label' => 'Tgl Invoice',    'key' => 'invoice_date',  'type' => 'date'],
            ['label' => 'Jatuh Tempo',    'key' => 'due_date',      'type' => 'date'],
            ['label' => 'Total Tagihan',  'key' => 'net_amount',    'type' => 'money'],
            ['label' => 'Nilai Alokasi',  'key' => 'alokasi',       'type' => 'money'],
            ['label' => 'Sisa Saldo',     'key' => 'outstanding_amount', 'type' => 'money'],
            ['label' => 'Metode',         'key' => 'method',        'type' => 'text'],
            ['label' => 'Jenis',          'key' => 'payment_type',  'type' => 'text'],
            ['label' => 'Bank',           'key' => 'bank_name',     'type' => 'text'],
            ['label' => 'No. Referensi',  'key' => 'reference_no',  'type' => 'text'],
            ['label' => 'Hari Penagihan', 'key' => 'hari_penagihan','type' => 'int'],
            ['label' => 'Hari Terlambat', 'key' => 'hari_terlambat','type' => 'int'],
            ['label' => 'Status',         'key' => 'status',        'type' => 'text'],
        ], $rows, ['payment_no' => 'TOTAL', 'alokasi' => array_sum(array_map(fn($r) => (float)$r['alokasi'], $rows))]);
    }
}
