<?php
declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\Request;
use App\Core\Validator;
use App\Models\Account;
use App\Models\AuditLog;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\User;
use App\Services\CommerceService;
use App\Services\DocumentStore;

final class InvoiceController extends BaseController
{
    public function index(Request $request): void
    {
        $this->requireModule('invoices');
        $status = (string) ($_GET['status'] ?? 'all');
        if (!in_array($status, ['all', 'unpaid', 'partial', 'paid', 'void'], true)) {
            $status = 'all';
        }
        $due = (string) ($_GET['due'] ?? 'all');
        if (!in_array($due, ['all', 'overdue', 'week', 'month'], true)) {
            $due = 'all';
        }
        $page = $this->page();
        $scope = $this->ownerScope();
        $assigned = (int) ($_GET['assigned'] ?? 0);
        $ownerFilter = $scope;
        if ($scope === null && $assigned > 0) {
            $ownerFilter = $assigned;
        }
        $accountId = (int) ($_GET['account'] ?? 0);
        $result = Invoice::paginate($ownerFilter, $status, $this->q(), $page, 20, $accountId, $due);
        view('invoices/index', [
            'title' => 'Invoices',
            'rows' => $result['rows'],
            'total' => $result['total'],
            'page' => $page,
            'pages' => max(1, (int) ceil($result['total'] / 20)),
            'q' => $this->q(),
            'status' => $status,
            'due' => $due,
            'assigned' => $assigned,
            'showAssigned' => $scope === null,
            'accountId' => $accountId,
            'counts' => Invoice::counts($ownerFilter),
            'accounts' => Account::allForSelect(),
            'owners' => User::salespeople((int) Auth::instance()->tenantId()),
            'orders' => Order::paginate($ownerFilter, 'all', '', 1, 80)['rows'],
            'notifications' => $this->notifications(),
        ]);
    }

    public function store(Request $request): void
    {
        $this->requireModule('invoices');
        $this->verifyCsrf($request);
        $data = $request->all();
        [$file, $fileName] = $this->requirePdf();
        if (!empty($data['order_id'])) {
            $order = Order::find((int) $data['order_id']);
            if (!$order) {
                json_out(['ok' => false, 'error' => 'Order not found.'], 404);
            }
            $id = CommerceService::invoiceFromOrder($order, $fileName);
            $this->savePdf($id, $file);
            AuditLog::write('invoice.create', 'invoice', $id);
            json_out(['ok' => true, 'id' => $id, 'redirect' => url('invoices/' . $id)]);
        }
        $v = (new Validator())->required($data, ['account_id' => 'Account', 'title' => 'Title']);
        if ($v->fails()) {
            $this->fail($v);
        }
        $account = Account::find((int) $data['account_id']);
        if (!$account) {
            json_out(['ok' => false, 'error' => 'Account not found.'], 404);
        }
        $id = Invoice::create([
            'account_id' => (int) $account['id'],
            'order_id' => null,
            'owner_id' => (int) Auth::instance()->id(),
            'number' => CommerceService::tempNumber(),
            'title' => $data['title'],
            'amount' => (float) ($data['amount'] ?? 0),
            'paid_amount' => 0,
            'status' => 'unpaid',
            'file_name' => $fileName,
            'due_date' => $data['due_date'] ?: date('Y-m-d', strtotime('+14 days')),
        ]);
        $this->savePdf($id, $file);
        CommerceService::assignNumber('invoices', $id, 'INV-');
        AuditLog::write('invoice.create', 'invoice', $id);
        json_out(['ok' => true, 'id' => $id, 'redirect' => url('invoices/' . $id)]);
    }

    public function show(Request $request): void
    {
        $this->requireModule('invoices');
        $row = Invoice::detail((int) $request->param('id'));
        if (!$row) {
            flash('error', 'Invoice not found.');
            redirect('invoices');
        }
        view('invoices/show', [
            'title' => $row['number'],
            'row' => $row,
            'notifications' => $this->notifications(),
        ]);
    }

    public function pay(Request $request): void
    {
        $this->requireModule('invoices');
        $this->verifyCsrf($request);
        $row = Invoice::find((int) $request->param('id'));
        if (!$row) {
            json_out(['ok' => false, 'error' => 'Not found.'], 404);
        }
        if (in_array((string) $row['status'], ['paid', 'void'], true)) {
            json_out(['ok' => false, 'error' => 'This invoice is already closed.'], 422);
        }
        $amt = (float) $request->input('amount', $row['amount']);
        if ($amt <= 0) {
            $amt = (float) $row['amount'] - (float) $row['paid_amount'];
        }
        Invoice::markPaid((int) $row['id'], $amt);
        AuditLog::write('invoice.pay', 'invoice', (int) $row['id']);
        json_out(['ok' => true, 'message' => 'Payment recorded.', 'redirect' => url('invoices/' . $row['id'])]);
    }

    public function replaceFile(Request $request): void
    {
        $this->requireModule('invoices');
        $this->verifyCsrf($request);
        $row = Invoice::find((int) $request->param('id'));
        if (!$row) {
            json_out(['ok' => false, 'error' => 'Not found.'], 404);
        }
        if (!empty($row['file_name'])) {
            json_out(['ok' => false, 'error' => 'This invoice already has a PDF.'], 422);
        }
        [$file, $fileName] = $this->requirePdf();
        $this->savePdf((int) $row['id'], $file);
        Invoice::update((int) $row['id'], ['file_name' => $fileName]);
        AuditLog::write('invoice.pdf', 'invoice', (int) $row['id']);
        json_out(['ok' => true, 'message' => 'Invoice PDF saved.', 'redirect' => url('invoices/' . $row['id'])]);
    }

    public function file(Request $request): void
    {
        $this->requireModule('invoices');
        $row = Invoice::find((int) $request->param('id'));
        if (!$row || empty($row['file_name'])) {
            http_response_code(404);
            echo 'Not found.';
            return;
        }
        $tid = (int) Auth::instance()->tenantId();
        if (!DocumentStore::existsEntity($tid, 'invoice', (int) $row['id'])) {
            http_response_code(404);
            echo 'No PDF on this invoice.';
            return;
        }
        DocumentStore::streamEntity($tid, 'invoice', (int) $row['id'], (string) $row['file_name']);
    }

    /** @return array{0: array<string, mixed>, 1: string} */
    private function requirePdf(): array
    {
        $file = is_array($_FILES['pdf'] ?? null) ? $_FILES['pdf'] : [];
        try {
            $fileName = DocumentStore::accept($file, 'Upload a PDF for this invoice.');
        } catch (\RuntimeException $e) {
            json_out(['ok' => false, 'error' => $e->getMessage()], 422);
        }
        return [$file, $fileName];
    }

    /** @param array<string, mixed> $file */
    private function savePdf(int $id, array $file): void
    {
        try {
            DocumentStore::saveEntity('invoice', $id, (string) $file['tmp_name']);
        } catch (\RuntimeException $e) {
            json_out(['ok' => false, 'error' => $e->getMessage()], 500);
        }
    }
}
