<?php
// app/Libraries/TransactionService.php
namespace App\Libraries;

use App\Models\EventFieldModel;
use App\Models\EventModel;
use App\Models\EventPackageModel;
use App\Models\ParticipantFieldValueModel;
use App\Models\ParticipantModel;
use App\Models\TransactionModel;
use App\Models\TransactionStatusLogModel;
use App\Models\UserModel;

class TransactionService
{
    /**
     * Mengirim notifikasi konfirmasi setelah transaksi berhasil dibuat.
     */
    public function finalizeTransaction(int $transactionId)
    {
        // Panggil metode getTransactionData dengan ID, tanpa validasi user
        $data = $this->getTransactionData($transactionId);
        if (!$data) return;

        (new EmailService())->sendTransactionPending($data['user']->email, $data);
    }

    public function generateInvoiceNumber(int $eventId): string
    {
        $db = \Config\Database::connect();
        $db->transBegin();
        
        try {
            $eventModel = new \App\Models\EventModel();
            $event = $eventModel->find($eventId);
            $format = $event->invoice_format ?? 'INV/{YYYY}{MM}/[SEQ:5]';

            $replacements = [
                '{YYYY}' => date('Y'), '{YY}' => date('y'), '{MM}' => date('m'), '{DD}' => date('d'),
                '{EVENT_ID}' => $event->id, '{EVENT_CODE}' => $event->event_code ?? 'EVT',
            ];
            $prefix = strtr($format, $replacements);

            if (preg_match('/\[SEQ:(\d+)\]/', $prefix, $matches)) {
                $padding = (int)$matches[1];
                $seqPlaceholder = $matches[0];
                $prefix = str_replace($seqPlaceholder, '', $prefix);

                $sql = "SELECT invoice_number FROM transactions WHERE invoice_number LIKE ? ORDER BY id DESC LIMIT 1 FOR UPDATE";
                $lastInvoiceRow = $db->query($sql, [$prefix . '%'])->getRow();
                
                $nextSeq = 1;
                if ($lastInvoiceRow) {
                    $lastSeqNum = (int)str_replace($prefix, '', $lastInvoiceRow->invoice_number);
                    $nextSeq = $lastSeqNum + 1;
                }

                $sequence = str_pad($nextSeq, $padding, '0', STR_PAD_LEFT);
                $invoiceNumber = $prefix . $sequence;

            } else {
                $invoiceNumber = $prefix . time();
            }

            $db->transCommit();
            return $invoiceNumber;

        } catch (\Exception $e) {
            $db->transRollback();
            log_message('error', 'Failed to generate invoice number: ' . $e->getMessage());
            return 'INV-ERR-' . time();
        }
    }
    
    /**
     * Menandai transaksi sebagai 'paid', membuat QR, membuat PDF, mengirim email, lalu menghapus PDF.
     */
    public function markAsPaid(int $transactionId): bool
    {
        $db = \Config\Database::connect();
        $db->transStart();

        try {
            $sql = "SELECT * FROM transactions WHERE id = ? FOR UPDATE";
            $transaction = $db->query($sql, [$transactionId])->getRow();

            // Idempotency: Jangan lakukan apa-apa jika tidak ditemukan atau sudah lunas
            if (!$transaction || $transaction->payment_status === 'paid') {
                $db->transRollback();
                return true; 
            }

            // 1. Update status transaksi
            (new TransactionModel())->update($transactionId, ['payment_status' => 'paid']);

            // 2. Ambil semua peserta di transaksi ini
            $participants = (new ParticipantModel())->where('transaction_id', $transactionId)->findAll();
            
            // 3. Increment kuota event
            (new EventModel())->where('id', $transaction->event_id)->increment('used_quota', count($participants));

            // 4. Hitung dan increment kuota untuk setiap paket
            $packagesInTrx = array_count_values(array_column($participants, 'event_package_id'));
            $packageModel = new EventPackageModel();
            foreach ($packagesInTrx as $packageId => $quantity) {
                $packageModel->where('id', $packageId)->increment('used_quota', $quantity);
            }
            
            // 5. Buat QR code
            (new TicketService())->generateQrForTransaction($transactionId);

            $db->transComplete();
            
            if ($db->transStatus() === false) {
                 log_message('error', '[TransactionService] DB transaction failed while marking as paid for trx ID: ' . $transactionId);
                 return false;
            }

            // 6. Kirim email (setelah transaksi DB berhasil)
            // Ambil data lagi untuk mendapatkan QR number yang baru dibuat
            $data = $this->getTransactionData($transactionId); 
            if ($data) {
                $pdfService = new PdfService();
                $pdfPath = $pdfService->createTicketPdf($data);
                if ($pdfPath) {
                    (new EmailService())->sendTransactionPaid($data['user']->email, $data, $pdfPath);
                    if (file_exists($pdfPath)) unlink($pdfPath);
                }
            }
            
            return true;

        } catch (\Exception $e) {
            if (isset($db) && $db->transStatus() !== false) {
                $db->transRollback();
            }
            log_message('error', '[TransactionService] Exception on markAsPaid. ID: ' . $transactionId . '. Error: ' . $e->getMessage());
            return false;
        }
    }

    public function changeStatus(int $transactionId, string $newStatus, int $adminId, bool $notifyUser = false, ?string $notes = null): bool
    {
        $transactionModel = new TransactionModel();
        $transaction = $transactionModel->find($transactionId);

        if (!$transaction || $transaction->payment_status === $newStatus) {
            return false; // Tidak ada perubahan
        }

        $previousStatus = $transaction->payment_status;

        // Logika utama untuk perubahan status
        if ($newStatus === 'paid') {
            // Gunakan metode markAsPaid yang sudah ada karena ia menangani QR, dll.
            return $this->markAsPaid($transactionId);
        }

        // Untuk perubahan status lainnya
        $transactionModel->update($transactionId, ['payment_status' => $newStatus]);
        
        // Catat log perubahan
        (new TransactionStatusLogModel())->insert([
            'transaction_id'  => $transactionId,
            'admin_id'        => $adminId,
            'previous_status' => $previousStatus,
            'new_status'      => $newStatus,
            'notes'           => $notes,
            'created_at'      => date('Y-m-d H:i:s')
        ]);

        if ($notifyUser) {
            $data = $this->getTransactionData($transactionId);
            if ($data) {
                $emailService = new EmailService();
                if ($newStatus === 'rejected') {
                    $emailService->sendTransactionRejected($data['user']->email, $data);
                } elseif ($newStatus === 'canceled') {
                    $emailService->sendTransactionCanceled($data['user']->email, $data);
                }
            }
        }
        
        return true;
    }

    public function resendNotification(int $transactionId): bool
    {
        $data = $this->getTransactionData($transactionId);
        if (!$data) {
            return false;
        }

        $emailService = new EmailService();
        $userEmail = $data['user']->email;
        $status = $data['transaction']->payment_status;

        // Tentukan notifikasi mana yang akan dikirim
        switch ($status) {
            case 'paid':
                $pdfService = new PdfService();
                $pdfPath = $pdfService->createTicketPdf($data);
                if ($pdfPath) {
                    $success = $emailService->sendTransactionPaid($userEmail, $data, $pdfPath);
                    if (file_exists($pdfPath)) unlink($pdfPath);
                    return $success;
                }
                return false;

            case 'pending':
                return $emailService->sendTransactionPending($userEmail, $data);

            case 'rejected':
                return $emailService->sendTransactionRejected($userEmail, $data);

            case 'canceled':
                return $emailService->sendTransactionCanceled($userEmail, $data);
            
            default:
                // Tidak melakukan apa-apa untuk status lain seperti 'deleted'
                return false;
        }
    }

    /**
     * Fungsi untuk mengambil semua data detail transaksi.
     */
    public function getTransactionData($identifier, ?int $requesterId = null): ?array
    {
        $transactionModel = new TransactionModel();
        
        $column = is_numeric($identifier) ? 'id' : 'uuid';
        
        $query = $transactionModel->where($column, $identifier);
        
        // Terapkan filter kepemilikan user frontend, jika ada.
        if ($requesterId !== null && session()->get('user_type') === 'user') {
            $query->where('user_id', $requesterId);
        }

        // 1. Ambil data transaksi dari database terlebih dahulu.
        $transaction = $query->first();

        // 2. Jika transaksi tidak ditemukan, langsung hentikan.
        if (!$transaction) {
            return null;
        }

        // 3. BARU lakukan pengecekan hak akses gereja untuk admin.
        if ($requesterId !== null && session()->get('user_type') === 'admin') {
            helper('general');
            $allowedChurchIds = getAdminChurchIds($requesterId);
            
            if ($allowedChurchIds !== null) {
                // Kita sudah punya $transaction, jadi kita bisa ambil event_id-nya.
                $event = (new EventModel())->find($transaction->event_id);
                
                // Jika event tidak terikat pada gereja yang diizinkan, tolak akses.
                if ($event && $event->church_id !== null && !in_array($event->church_id, $allowedChurchIds)) {
                    return null; // Kembalikan null seolah-olah transaksi tidak ditemukan.
                }
            }
        }
        
        // 4. Jika semua pengecekan lolos, lanjutkan mengambil sisa data.
        $participantModel = new ParticipantModel();
        $participants = $participantModel
            ->select('participants.*, event_packages.name as package_name, event_packages.price as package_price, churches.name as joined_church_name')
            ->join('event_packages', 'event_packages.id = participants.event_package_id')
            ->join('churches', 'churches.id = participants.church_id', 'left')
            ->where('participants.transaction_id', $transaction->id)
            ->findAll();

        $eventFieldModel = new EventFieldModel();
        $eventFields = $eventFieldModel
            ->where('event_id', $transaction->event_id)
            ->orderBy('sort_order', 'ASC')
            ->findAll();
        
        $participantIds = array_column($participants, 'id');
        $fieldValues = [];
        if (!empty($participantIds)) {
            $fieldValueModel = new ParticipantFieldValueModel();
            $rawFieldValues = $fieldValueModel->whereIn('participant_id', $participantIds)->findAll();
            foreach ($rawFieldValues as $value) {
                $fieldValues[$value->participant_id][$value->event_field_id] = $value->value;
            }
        }
        
        foreach ($participants as $p) {
            $p->custom_fields = [];
            foreach ($eventFields as $eventField) {
                $value = $fieldValues[$p->id][$eventField->id] ?? null;
                if ($value !== null) {
                     $p->custom_fields[] = (object)['label' => $eventField->label, 'value' => $value];
                }
            }
        }

        return [
            'transaction'  => $transaction,
            'participants' => $participants,
            'event'        => (new EventModel())->find($transaction->event_id),
            'user'         => (new UserModel())->find($transaction->user_id),
        ];
    }
}