<?php

namespace App\Controllers\Frontend;

use App\Controllers\BaseController;
use App\Models\ChurchModel;
use App\Models\EventModel;
use App\Models\EventPackageModel;
use App\Models\EventFieldModel;
use App\Models\EventPaymentMethodModel;
use App\Models\TransactionModel;
use App\Models\ParticipantModel;
use App\Models\ParticipantFieldValueModel;
use App\Models\PromotionModel;
use App\Libraries\PdfService;
use App\Libraries\TransactionService;
use Ramsey\Uuid\Uuid;


class TransactionController extends BaseController
{
    /**
     * Langkah 1: Menampilkan halaman untuk memilih Event.
     */
    public function new()
    {
        $eventModel = new EventModel();
        $data = [
            'events' => $eventModel->where('is_active', 1)
                                   ->where('(quota = 0 OR used_quota < quota)')
                                   ->orderBy('start_date', 'ASC')->findAll(),
        ];
        return view('frontend/transaction/new', $data);
    }
    
    public function showParticipantForm(string $slug)
    {
        $eventModel = new EventModel();
        $event = $eventModel->where('slug', $slug)
                            ->where('is_active', 1)
                            ->where('(quota = 0 OR used_quota < quota)')
                            ->first();

        if (!$event) {
            return redirect()->route('transaction.new')->with('error', lang('Transaction.error_event_not_found'));
        }

        $packageModel = new EventPackageModel();
        $today = date('Y-m-d');
        $activePackages = $packageModel
            ->where('deleted_at', null)->where('event_id', $event->id)
            ->where('start_date <=', $today)->where('end_date >=', $today)
            ->where('(quota = 0 OR used_quota < quota)')
            ->findAll();

        if (empty($activePackages)) {
            return redirect()->route('transaction.new')->with('error', lang('Transaction.error_transaction_not_found'));
        }

        $fieldModel = new EventFieldModel();
        $customFields = $fieldModel->where('event_id', $event->id)->orderBy('sort_order', 'ASC')->findAll();
        foreach ($customFields as $field) {
            if ($field->type === 'select' && !empty($field->options)) {
                $field->options = json_decode($field->options, true);
            }
        }

        $data = [
            'event'          => $event,
            'packages'       => $activePackages,
            'customFields'   => $customFields,
            'paymentMethods' => (new EventPaymentMethodModel())->where('event_id', $event->id)->where('is_active', 1)->findAll(),
            'churches'       => (new ChurchModel())->findAllActive(),
        ];
        
        return view('frontend/transaction/new_step2', $data);
    }

    public function show(string $uuid)
    {
        $transactionService = new TransactionService();
        $data = $transactionService->getTransactionData($uuid, session()->get('user_id'));
        if (!$data) {
            return redirect()->route('dashboard.show')->with('error', lang('Transaction.error_transaction_not_found'));
        }
        return view('frontend/transaction/show', $data);
    }


    public function create()
    {
        if (session()->has('processing_transaction')) {
            return redirect()->back()->with('error', lang('Transaction.error_processing'));
        }
        session()->set('processing_transaction', true);
        
        try {
            if (!$this->validate($this->getTransactionValidationRules())) {
                return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
            }

            $participantsData = $this->request->getPost('participants');
            $eventId = (int)$this->request->getPost('event_id');
            $promoCode = $this->request->getPost('promo_code');
            $paymentMethodName = $this->request->getPost('payment_method');
            
            $subTotal = 0;
            $packageModel = new EventPackageModel();
            foreach ($participantsData as $pData) {
                $package = $packageModel->find($pData['package_id']);
                if ($package) $subTotal += $package->price;
            }
            
            // Lakukan validasi promo awal
            $promoData = $this->calculateDiscount($promoCode, $subTotal, count($participantsData), $eventId);
            if ($promoData['error']) {
                return redirect()->back()->withInput()->with('error', $promoData['error']);
            }
            
            $db = \Config\Database::connect();
            $db->transStart();

            $eventSql = "SELECT * FROM events WHERE id = ? FOR UPDATE";
            $event = $db->query($eventSql, [$eventId])->getRow();
            if (!$event || ($event->quota > 0 && (($event->used_quota + count($participantsData)) > $event->quota))) {
                $db->transRollback();
                return redirect()->back()->with('error', lang('Transaction.error_event_capacity'));
            }

            $packageSql = "SELECT * FROM event_packages WHERE id = ? FOR UPDATE";
            $packagesInCart = array_count_values(array_column($participantsData, 'package_id'));
            foreach($packagesInCart as $packageId => $quantity) {
                $package = $db->query($packageSql, [$packageId])->getRow();
                if (!$package || ($package->quota > 0 && (($package->used_quota + $quantity) > $package->quota))) {
                    $db->transRollback();
                    return redirect()->back()->withInput()->with('error', lang('Transaction.error_package_insufficient', [$package->name]));
                }
            }

            // Lakukan validasi promo kedua (race condition check) di dalam transaksi
            if ($promoData['promotion_id']) {
                $promoSql = "SELECT * FROM promotions WHERE id = ? FOR UPDATE";
                $promo = $db->query($promoSql, [$promoData['promotion_id']])->getRow();
                if (!$promo || ($promo->quota > 0 && ($promo->used_quota >= $promo->quota))) {
                    $db->transRollback();
                    return redirect()->back()->withInput()->with('error', lang('Transaction.error_promo_quota_full'));
                }
                (new PromotionModel())->where('id', $promoData['promotion_id'])->increment('used_quota', 1);
            }

            $paymentMethodModel = new EventPaymentMethodModel();
            $paymentMethod = $paymentMethodModel->where('event_id', $eventId)->where('method', $paymentMethodName)->first();
            $handlingFee = $paymentMethod ? (float)$paymentMethod->fee : 0;
            $totalAmount = $subTotal - $promoData['discount_amount'] + $handlingFee;
            $uuid = Uuid::uuid4()->toString();
            $transactionService = new \App\Libraries\TransactionService();

            $transactionModel = new TransactionModel();
            $dataToInsert = [
                'uuid' => $uuid, 
                'user_id' => (int)session()->get('user_id'), 
                'event_id' => $eventId,
                'invoice_number' => $transactionService->generateInvoiceNumber($eventId),
                'payment_type' => $paymentMethodName, 
                'payment_status' => 'pending',
                'total_quantity' => count($participantsData), 
                'subtotal' => (float)$subTotal, 
                'total_discount' => (float)$promoData['discount_amount'], 
                'handling_fee' => $handlingFee, 
                'total_amount' => (float)$totalAmount, 
                'promotion_id' => $promoData['promotion_id'],
            ];

            $transactionId = $transactionModel->insert($dataToInsert, true);

            // --- TAMBAHKAN BLOK DEBUGGING INI ---
            if ($transactionId === false) {
                // Jika insert gagal, catat error spesifik dari model
                $errors = $transactionModel->errors();
                log_message('critical', 'Transaction insert failed. Errors: ' . json_encode($errors));
                log_message('critical', 'Data attempted: ' . json_encode($dataToInsert));

                // Batalkan transaksi dan kembalikan dengan error
                $db->transRollback();
                return redirect()->back()->withInput()->with('error', 'Failed to create transaction record. Please check the logs.');
            }

            $participantModel = new ParticipantModel();
            $fieldValueModel = new ParticipantFieldValueModel();

            foreach ($participantsData as $pData) {
                $participantId = $participantModel->insert([
                    'user_id' => session()->get('user_id'), 'transaction_id' => $transactionId,
                    'event_package_id' => $pData['package_id'], 
                    'church_id' => $pData['church_id'] === 'other' ? null : $pData['church_id'], 
                    'church_name' => $pData['church_id'] === 'other' ? $pData['church_name'] : null,
                    'name' => $pData['name'], 'phone_number' => $pData['phone_number'], 'email' => $pData['email'],
                    'gender' => $pData['gender'], 'kta_number' => $pData['kta_number'] ?? null,
                    'birth_date' => !empty($pData['birth_date']) ? $pData['birth_date'] : null,
                ], true);

                $customFieldsData = $pData['custom'] ?? [];
                $valuesToInsert = [];
                foreach ($customFieldsData as $fieldId => $value) {
                    if (empty($value) && $value !== '0') continue;
                    $valueToSave = is_array($value) ? json_encode($value) : $value;
                    $valuesToInsert[] = ['participant_id' => $participantId, 'event_field_id' => $fieldId, 'value' => $valueToSave];
                }

                if (!empty($valuesToInsert)) $fieldValueModel->insertBatch($valuesToInsert);
            }

            $db->transComplete();

            if ($db->transStatus() === false) {
                 return redirect()->back()->withInput()->with('error', lang('Transaction.error_db_save'));
            }

            
            $transactionService->finalizeTransaction($transactionId);

            if ($paymentMethodName === 'va') {
                $transaction = $transactionModel->find($transactionId);
                return $this->initiatePayment($transaction);
            }
            
            return redirect()->route('transaction.show', [$uuid])->with('success', lang('Transaction.success_created'));

        } catch (\Exception $e) {
            if (isset($db) && $db->transStatus() !== false) {
                $db->transRollback();
            }
            log_message('error', 'Transaction creation failed: ' . $e->getMessage());
            return redirect()->back()->withInput()->with('error', lang('Transaction.error_unexpected'));
        } finally {
            session()->remove('processing_transaction');
        }
    }

    public function validatePromo()
    {
        $this->response->setHeader('Content-Type', 'application/json');
        
        $promoCode = $this->request->getPost('promo_code');
        $subTotal = (float)$this->request->getPost('subtotal');
        $quantity = (int)$this->request->getPost('quantity');
        $eventId = (int)$this->request->getPost('event_id');

        $promoData = $this->calculateDiscount($promoCode, $subTotal, $quantity, $eventId);

        if (isset($promoData['error']) && $promoData['error']) {
            return $this->response->setStatusCode(422)->setJSON(['error' => $promoData['error']]);
        }
        
        $promoData['code'] = strtoupper($promoCode);
        return $this->response->setJSON($promoData);
    }
    
    public function downloadPdf(string $uuid)
    {
        $pdfService = new PdfService();
        $userId = session()->get('user_id');

        $result = $pdfService->streamTicket($uuid, $userId);

        if (is_array($result) && isset($result['error'])) {
            return redirect()->back()->with('error', $result['error']);
        }
        return;
    }

    private function getTransactionValidationRules(): array
    {
        $participantsData = $this->request->getPost('participants') ?? [];
        $eventId = (int)$this->request->getPost('event_id');
        
        $rules = [
            'event_id'       => ['label' => 'Event', 'rules' => 'required|is_natural_no_zero'],
            'payment_method' => ['label' => 'Payment Method', 'rules' => 'required'],
            'participants'   => ['label' => 'Participant Data', 'rules' => 'has_no_duplicate_kta'],
            'agree_terms'    => ['label' => 'Terms and Conditions', 'rules' => 'required'],
        ];

        if (count($participantsData) < 1) {
            $rules['participants'] = ['label' => 'Participants', 'rules' => 'required', 'errors' => ['required' => 'You must add at least one participant.']];
        } elseif (count($participantsData) > 5) {
            $rules['participants'] = ['label' => 'Participants', 'rules' => 'if_exist|max_length[0]', 'errors' => ['max_length' => 'You can only register a maximum of 5 participants per transaction.']];
        }

        $packageModel = new EventPackageModel();
        $packagesMap = array_column($packageModel->where('event_id', $eventId)->findAll(), null, 'id');
        
        $fieldModel = new EventFieldModel();
        $customFields = $fieldModel->where('event_id', $eventId)->findAll();

        foreach ($participantsData as $index => $pData) {
            $prefix = "participants.{$index}";
            $pNum = $index + 1;
            
            $rules["{$prefix}.package_id"] = ['label' => "P#{$pNum} Package", 'rules' => 'required|is_natural_no_zero'];
            $rules["{$prefix}.name"] = ['label' => "P#{$pNum} Name", 'rules' => 'required|alpha_space'];
            $rules["{$prefix}.email"] = ['label' => "P#{$pNum} Email", 'rules' => 'required|valid_email'];
            $rules["{$prefix}.phone_number"] = ['label' => "P#{$pNum} Phone", 'rules' => 'required|min_length[10]'];
            
            $rules["{$prefix}.church_id"] = ['label' => "P#{$pNum} Church", 'rules' => 'required'];
            if (($pData['church_id'] ?? '') === 'other') {
                $rules["{$prefix}.church_name"] = ['label' => "P#{$pNum} Other Church Name", 'rules' => 'required|string|max_length[150]'];
            }

            $package = $packagesMap[$pData['package_id'] ?? null] ?? null;

            if ($package) {
                $isKtaFilled = !empty($pData['kta_number']);
                $isKtaRequiredByPackage = ($package->require_kta == 1);
                $ktaRules = 'permit_empty';
                if ($isKtaRequiredByPackage) $ktaRules = 'required';
                if ($isKtaFilled) $ktaRules .= "|is_unique_participant_kta[event_id]|validate_kta_and_birthdate[{$prefix}.birth_date]";
                $rules["{$prefix}.kta_number"] = ['label' => "P#{$pNum} KTA", 'rules' => $ktaRules];
                $rules["{$prefix}.birth_date"] = ['label' => "P#{$pNum} Date of Birth", 'rules' => 'permit_empty|valid_date'];
                
                $genderRule = 'required|in_list[Male,Female]';
                if ($package->gender_rule !== 'All') $genderRule .= "|in_list[{$package->gender_rule}]";
                $rules["{$prefix}.gender"] = ['label' => "P#{$pNum} Gender", 'rules' => $genderRule];
            }

            foreach ($customFields as $field) {
                if ($field->is_required == 1) {
                    $rules["{$prefix}.custom.{$field->id}"] = ['label' => esc($field->label), 'rules' => 'required'];
                }
            }
        }
        
        return $rules;
    }
    
    private function generateInvoiceNumber(int $eventId): string
    {
        $eventModel = new 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);

            $transactionModel = new TransactionModel();
            $lastInvoice = $transactionModel->like('invoice_number', $prefix, 'after')->orderBy('id', 'DESC')->first();
            
            $nextSeq = 1;
            if ($lastInvoice) {
                $lastSeqNum = (int)str_replace($prefix, '', $lastInvoice->invoice_number);
                $nextSeq = $lastSeqNum + 1;
            }

            $sequence = str_pad($nextSeq, $padding, '0', STR_PAD_LEFT);
            return $prefix . $sequence;
        }
        
        return $prefix . time();
    }
    
    private function calculateDiscount(?string $promoCode, float $subTotal, int $quantity, int $eventId): array
    {
        $defaultResult = ['promotion_id' => null, 'discount_amount' => 0, 'error' => null];
        if (empty($promoCode)) return $defaultResult;

        $promoModel = new PromotionModel();
        $promo = $promoModel
            ->where('LOWER(code)', strtolower($promoCode))
            ->where('event_id', $eventId)
            ->where('is_active', 1)
            ->where('start_date <=', date('Y-m-d'))
            ->where('end_date >=', date('Y-m-d'))
            ->first();

        if (!$promo) { return ['error' => lang('Transaction.error_promo_not_found')] + $defaultResult; }
        if ($promo->quota > 0 && ($promo->used_quota >= $promo->quota)) { return ['error' => lang('Transaction.error_promo_quota_full')] + $defaultResult; }
        if ($promo->min_quantity > 0 && $quantity < $promo->min_quantity) { return ['error' => lang('Transaction.error_promo_min_qty', [$promo->min_quantity])] + $defaultResult; }
        if ($promo->max_quantity !== null && $quantity > $promo->max_quantity) { return ['error' => lang('Transaction.error_promo_max_qty', [$promo->max_quantity])] + $defaultResult; }
        if ($promo->min_amount > 0 && $subTotal < $promo->min_amount) { return ['error' => lang('Transaction.error_promo_min_amount', [number_format($promo->min_amount)])] + $defaultResult; }
        if ($promo->max_amount !== null && $subTotal > $promo->max_amount) { return ['error' => lang('Transaction.error_promo_max_amount', [number_format($promo->max_amount)])] + $defaultResult; }
        
        $discountAmount = 0;
        if ($promo->discount_amount > 0) {
            $discountAmount = (float)$promo->discount_amount;
        } elseif ($promo->discount_percent > 0) {
            $discountAmount = $subTotal * ($promo->discount_percent / 100);
        }

        return ['promotion_id' => $promo->id, 'discount_amount' => $discountAmount, 'error' => null];
    }

    private function initiatePayment(object $transaction)
    {
        $userModel = new \App\Models\UserModel();
        $participantModel = new ParticipantModel();
        $eventModel = new EventModel();
        
        $user = $userModel->find(session()->get('user_id'));
        $participants = $participantModel
            ->select('participants.*, event_packages.name as package_name, event_packages.price as package_price')
            ->join('event_packages', 'event_packages.id = participants.event_package_id')
            ->where('participants.transaction_id', $transaction->id)
            ->findAll();
        $event = $eventModel->find($transaction->event_id);

        if (!$user || empty($participants) || !$event) {
            log_message('error', 'Failed to initiate payment for transaction ID: ' . $transaction->id);
            return redirect()->route('transaction.show', [$transaction->uuid])->with('error', lang('Transaction.error_payment_init'));
        }

        helper(['text', 'general']);
        $lineItems = [];
        foreach ($participants as $p) {
            $lineItems[] = [
                'name'     => sanitize_alphanumeric_space($p->package_name),
                'quantity' => 1, 'price' => (int)$p->package_price,
                'sku'      => url_title($p->package_name, '-', true), 'type' => $event->name
            ];
        }
        
        $payload = [
            'channel_id' => getenv('payment.api.channelId'),
            'order' => [
                'amount'                => (int)$transaction->total_amount, 'invoice_number' => $transaction->invoice_number,
                'currency'              => 'IDR',
                'callback_url'          => site_url(route_to('dashboard.show')), 'callback_url_cancel'   => site_url(route_to('dashboard.show')),
                'callback_url_result'   => site_url(route_to('dashboard.show')), 'auto_redirect' => true,
            ],
            'payment' => ['payment_due_date' => getenv('payment.api.payment_due_date')],
            'line_items' => $lineItems,
            'customer' => [
                'id'    => getenv('payment.api.prefix_customer_id') . $user->id, 'name'  => $user->name,
                'phone' => $user->phone_number, 'email' => $user->email,
            ]
        ];

        try {
            $client = \Config\Services::curlrequest(['timeout' => 20]);
            $response = $client->post(getenv('payment.api.url'), [
                'headers' => ['Content-Type'  => 'application/json', 'Authorization' => 'Bearer ' . getenv('payment.api.token')],
                'json' => $payload, 'http_errors' => false
            ]);

            $outerBody = json_decode($response->getBody());
            
            if ($response->getStatusCode() !== 200 || !isset($outerBody->response)) {
                $errorMessage = $outerBody->message ?? 'Payment gateway returned an invalid initial response.';
                log_message('error', 'Payment Gateway Initial Error for Invoice ' . $transaction->invoice_number . ': ' . $response->getBody());
                return redirect()->route('transaction.show', [$transaction->uuid])->with('error', lang('Transaction.error_payment_session', [$errorMessage]));
            }

            $innerBody = json_decode($outerBody->response);
            $paymentUrl = $innerBody->response->payment->url ?? null;

            if ($paymentUrl) {
                (new TransactionModel())->update($transaction->id, ['payment_url' => $paymentUrl]);
                return redirect()->to($paymentUrl);
            }

            $errorMessage = $innerBody->message[0] ?? 'Payment URL not found in the gateway response.';
            log_message('error', 'Payment Gateway URL Missing for Invoice ' . $transaction->invoice_number . ': ' . $outerBody->responses);
            return redirect()->route('transaction.show', [$transaction->uuid])->with('error', lang('Transaction.error_payment_url', [$errorMessage]));

        } catch (\Exception $e) {
            log_message('error', 'CURLRequest to Payment Gateway failed: ' . $e->getMessage());
            return redirect()->route('transaction.show', [$transaction->uuid])->with('error', lang('Transaction.error_payment_connect'));
        }
    }

    public function showTerms(string $slug)
    {
        $event = (new EventModel())->where('slug', $slug)->first();
        if (!$event || empty($event->terms_conditions)) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }

        $data = [
            'event' => $event,
            'pageTitle' => 'Terms & Conditions - ' . $event->name,
        ];
        return view('frontend/transaction/terms', $data);
    }
}