<?php

namespace App\Controllers;
use CodeIgniter\RESTful\ResourceController;
use App\Models\PolicyModel;
use App\Models\EnquiryModel;
use App\Models\QuotationModel;
use App\Models\InvoiceModel;
use App\Models\InvoiceItemModel;
use App\Models\InvoiceUtrModel;
use App\Models\AgentIncentiveFileModel;
use CodeIgniter\Database\Exceptions\DataException;
use PhpOffice\PhpSpreadsheet\IOFactory;

class InvoiceController extends ResourceController
{
    protected $PolicyModel;
    protected $QuotationModel;
    protected $EnquiryModel;
    protected $InvoiceModel;
    protected $InvoiceItemModel;
    protected $InvoiceUtrModel;
    protected $AgentIncentiveFileModel;
    protected $db;

    public function __construct()
    {
        $this->PolicyModel = new PolicyModel();
        $this->QuotationModel = new QuotationModel();
        $this->EnquiryModel = new EnquiryModel();
        $this->InvoiceModel = new InvoiceModel();
        $this->InvoiceItemModel = new InvoiceItemModel();
        $this->InvoiceUtrModel = new InvoiceUtrModel();
        $this->AgentIncentiveFileModel = new AgentIncentiveFileModel();
        $this->db = \Config\Database::connect();
    }

    public function invoiceList()
    {
        try {
            $fromDateRaw = $this->request->getGet('from_date');
            $toDateRaw   = $this->request->getGet('to_date');

            $fromParsed = $this->parseOptionalDmYDate($fromDateRaw);
            $toParsed   = $this->parseOptionalDmYDate($toDateRaw);

            if ($fromParsed['error'] !== null) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'data'   => $fromParsed['error'],
                ], 400);
            }
            if ($toParsed['error'] !== null) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'data'   => $toParsed['error'],
                ], 400);
            }

            if ($fromParsed['ymd'] !== null && $toParsed['ymd'] !== null && $fromParsed['ymd'] > $toParsed['ymd']) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'data'   => 'from_date must be on or before to_date',
                ], 400);
            }

            $query = $this->InvoiceModel
                            ->select('partner_invoice.*,
                                    PB.name as broker_name, 
                                    pos.name as pos_name,
                                    FORMAT(partner_invoice.invoice_amount, 2, "en_IN") AS invoice_amount_indian_format,
                                    DATE_FORMAT(partner_invoice.invoice_date, "%d-%m-%Y") AS invoice_date_ui_format,
                                    (
                                        SELECT GROUP_CONCAT(pa.name ORDER BY pa.id SEPARATOR ", ")
                                        FROM partner_agent pa
                                        WHERE JSON_SEARCH(partner_invoice.agent_id, "one", CAST(pa.id AS CHAR)) IS NOT NULL
                                    ) AS partner_names,
                                    (
                                        SELECT GROUP_CONCAT(piu.utr_no ORDER BY piu.id SEPARATOR ", ")
                                        FROM partner_invoice_utr piu
                                        WHERE piu.invoice_id = partner_invoice.id
                                        AND piu.is_active = 1
                                    ) AS utr_numbers,
                                    (
                                        SELECT GROUP_CONCAT(DISTINCT pp.vehicle_type ORDER BY pp.vehicle_type SEPARATOR ", ")
                                        FROM partner_invoice_items pii
                                        LEFT JOIN partner_policy pp ON pp.id = pii.policy_id
                                        WHERE pii.invoice_id = partner_invoice.id
                                        AND pii.is_active = 1
                                    ) AS vehicle_types,
                                    partner_invoice.invoice_amount AS invoiced_amount,
                                    (
                                        COALESCE((
                                            SELECT SUM(piu.amount)
                                            FROM partner_invoice_utr piu
                                            WHERE piu.invoice_id = partner_invoice.id
                                            AND piu.is_active = 1
                                        ), 0.00)
                                    ) AS payout_amount,
                                    (
                                        partner_invoice.invoice_amount - (
                                            COALESCE((
                                                SELECT SUM(piu.amount)
                                                FROM partner_invoice_utr piu
                                                WHERE piu.invoice_id = partner_invoice.id
                                                AND piu.is_active = 1
                                            ), 0.00)
                                        )
                                    ) AS balance_amount', 
                                false) 
                            ->join('partner_brokers PB', 'PB.id = partner_invoice.broker_id', 'left')
                            ->join('partner_pos pos', 'pos.id = partner_invoice.pos_id', 'left')
                            ->where('partner_invoice.is_active', 1);

            if ($fromParsed['ymd'] !== null) {
                $query->where('DATE(partner_invoice.invoice_date) >=', $fromParsed['ymd']);
            }
            if ($toParsed['ymd'] !== null) {
                $query->where('DATE(partner_invoice.invoice_date) <=', $toParsed['ymd']);
            }

            $data = $query->orderBy('partner_invoice.id', 'DESC')->findAll();

            return $this->respond(['status' => 'success', 'code'   => 200,'data'   => $data ], 200);

        } catch (\Exception $e) {
            return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
        }
    }

    /**
     * Optional invoice list date filter: format d-m-Y (e.g. 10-03-2026). Empty / null = no filter.
     *
     * @return array{ymd: ?string, error: ?string}
     */
    private function parseOptionalDmYDate($value): array
    {
        if ($value === null) {
            return ['ymd' => null, 'error' => null];
        }
        $v = trim((string) $value);
        if ($v === '') {
            return ['ymd' => null, 'error' => null];
        }

        $dt = \DateTime::createFromFormat('d-m-Y', $v);
        if ($dt === false || $dt->format('d-m-Y') !== $v) {
            return [
                'ymd'   => null,
                'error' => 'Invalid date format; use d-m-Y (e.g. 10-03-2026)',
            ];
        }

        return ['ymd' => $dt->format('Y-m-d'), 'error' => null];
    }

    public function findInvoiceWithItems()
    {
        try {
            $id = $this->request->getGet('id');

            if (!$id) {
                return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'ID is required'], 400);
            }

            $invoice = $this->InvoiceModel->where('id', $id)->first();

            if (!$invoice) {
                return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'Invoice not found'], 404);
            }

            $items = $this->InvoiceItemModel->select('partner_invoice_items.* , pp.issued_date , pe.name as customer_name , pq.premium_amount')
                                            ->join('partner_policy pp','pp.id = partner_invoice_items.policy_id ', 'left')
                                            ->join('partner_enquiry pe','pe.id = pp.enquiry_id ', 'left')
                                            ->join('partner_quotation pq','pq.id = pp.quotation_id ', 'left')
                                            ->where('partner_invoice_items.invoice_id', $id)
                                            ->where('partner_invoice_items.is_active', 1)
                                            ->findAll();

            $paymentHistory = $this->InvoiceUtrModel
                                    ->select('id, invoice_id, amount as paid_amount, utr_no, utr_date as paid_date, created_at, created_by')
                                    ->where('invoice_id', $id)
                                    ->where('is_active', 1)
                                    ->where('amount >', 0)
                                    ->orderBy('id', 'DESC')
                                    ->findAll();

            $utrPaidAmount = (float) (
                $this->InvoiceUtrModel
                    ->selectSum('amount', 'total')
                    ->where('invoice_id', $id)
                    ->where('is_active', 1)
                    ->first()['total'] ?? 0
            );

            $totalPaidAmount = $utrPaidAmount;
            $invoiceAmount = (float) ($invoice['invoice_amount'] ?? 0);
            $balanceAmount = max($invoiceAmount - $totalPaidAmount, 0);


            return $this->respond([
                'status' => 'success',
                'code'   => 200,
                'data'   => [
                    'invoice' => $invoice,
                    'items'   => $items,
                    'payment_history' => $paymentHistory,
                    'paid_amount' => $totalPaidAmount,
                    'balance_amount' => $balanceAmount
                ]
            ], 200);

        } catch (\Exception $e) {
            return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
        }
    }

    /**
     * GET invoice_id, manager_id — invoice header + active line items (policies under this manager only).
     */
    public function invoiceView()
    {
        try {
            $invoiceId = (int) $this->request->getGet('invoice_id');
            $managerId = (int) $this->request->getGet('manager_id');

            if ($invoiceId <= 0 || $managerId <= 0) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'data'   => 'invoice_id and manager_id are required',
                ], 400);
            }

            $invoice = $this->InvoiceModel
                ->where('id', $invoiceId)
                ->where('is_active', 1)
                ->first();

            if (! $invoice) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 404,
                    'data'   => 'Invoice not found',
                ], 404);
            }

            $forbidden = $this->db->query(
                'SELECT 1 AS x FROM partner_invoice_items pii '
                . 'INNER JOIN partner_policy pp ON pp.id = pii.policy_id '
                . 'WHERE pii.invoice_id = ? AND pii.is_active = 1 '
                . 'AND (pp.manager_id IS NULL OR pp.manager_id != ?) '
                . 'LIMIT 1',
                [$invoiceId, $managerId]
            )->getRow();

            if ($forbidden !== null) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 403,
                    'data'   => 'You do not have access to this invoice',
                ], 403);
            }

            $items = $this->InvoiceItemModel
                ->select(
                    'partner_invoice_items.*, pp.vehicle_type, pp.issued_date, pe.name AS customer_name, pp.premium_amount, '
                    . 'pa.agent_code, pa.name AS agent_name, CONCAT_WS(" - ", pa.agent_code, pa.name) AS agent_code_name',
                    false
                )
                ->join('partner_policy pp', 'pp.id = partner_invoice_items.policy_id', 'left')
                ->join('partner_enquiry pe', 'pe.id = pp.enquiry_id', 'left')
                ->join('partner_quotation pq', 'pq.id = pp.quotation_id', 'left')
                ->join('partner_agent pa', 'pa.id = pp.agent_id', 'left')
                ->where('partner_invoice_items.invoice_id', $invoiceId)
                ->where('partner_invoice_items.is_active', 1)
                ->findAll();

            return $this->respond([
                'status' => 'success',
                'code'   => 200,
                'data'   => [
                    'invoice' => $invoice,
                    'items'   => $items,
                ],
            ], 200);
        } catch (\Exception $e) {
            return $this->respond([
                'status' => 'failed',
                'code'   => 500,
                'data'   => $e->getMessage(),
            ], 500);
        }
    }

    public function createOrUpdateInvoice()
    {
        $this->db->transBegin();

        try {
            $input = $this->request->getJSON(true);

            // Normalize pos_id
            $posIdRaw = $input['pos_id'] ?? 0;
            $posId = is_numeric($posIdRaw) ? (int)$posIdRaw : 0;

            $payoutStatus = isset($input['payout_status']) ? (int) $input['payout_status'] : 1;

            $invoiceId = $input['id'] ?? null;
            $isCreate  = empty($invoiceId);

            // ─────────────────────────────────────────────────────────────
            // PRE-SAVE VALIDATION: UTR checks (only on CREATE)
            // Run BEFORE any insert/update so nothing is written on failure
            // ─────────────────────────────────────────────────────────────
            if ($isCreate && !empty($input['utrs']) && is_array($input['utrs'])) {

                $seenUtrs = [];

                foreach ($input['utrs'] as $utrRow) {

                    $utrNo = trim((string)($utrRow['utr_no'] ?? ''));

                    if ($utrNo === '') {
                        continue;
                    }

                    // 1. Duplicate within the same request
                    if (in_array($utrNo, $seenUtrs, true)) {
                        $this->db->transRollback();
                        return $this->respond([
                            'status'  => 'failed',
                            'code'    => 400,
                            'message' => "Duplicate UTR found: \"{$utrNo}\" appears more than once in your submission. Please remove the duplicate and try again.",
                        ], 400);
                    }

                    $seenUtrs[] = $utrNo;

                    // 2. Already exists in DB (used in another payout)
                    $exists = $this->InvoiceUtrModel
                        ->where('utr_no', $utrNo)
                        ->where('is_active', 1)
                        ->first();

                    if ($exists) {
                        $this->db->transRollback();
                        return $this->respond([
                            'status'  => 'failed',
                            'code'    => 400,
                            'message' => "UTR \"{$utrNo}\" is already linked to another payout. Please verify the UTR number or contact support if you believe this is an error.",
                        ], 400);
                    }
                }
            }
            // ─────────────────────────────────────────────────────────────

            // Basic invoice data
            $invoiceData = [
                'invoice_amount' => $input['invoice_amount'] ?? 0,
                'broker_id'      => $input['broker_id'] ?? 0,
                'agent_id'       => json_encode($input['agent_id'] ?? []),
                'pos_id'         => $posId,
                'till_date'      => $input['till_date'] ? date('Y-m-d', strtotime($input['till_date'])) : null,
                'invoice_date'   => $input['invoice_date'] ? date('Y-m-d', strtotime($input['invoice_date'])) : null,
                'payout_status'  => $payoutStatus,
                'is_active'      => 1,
                'updated_at'     => date('Y-m-d H:i:s'),
                'updated_by'     => $input['updated_by'] ?? 0
            ];

            if (!$invoiceId) {

                //  Generate invoice number only for CREATE
                $invoiceData['invoice_no'] = $this->generateInvoiceNo($posId);

                $invoiceData['created_at'] = date('Y-m-d H:i:s');
                $invoiceData['created_by'] = $input['created_by'] ?? 0;

                $invoiceId = $this->InvoiceModel->insert($invoiceData);

                if ($invoiceId === false) {
                    $this->db->transRollback();
                    log_message('error', json_encode($this->InvoiceModel->errors()));
                    return $this->respond(['status' => 'failed', 'message' => 'Failed to create invoice', 'error' => json_encode($this->InvoiceModel->errors())], 500);
                }
            } else {

                // Invoice number should NOT change on update
                unset($invoiceData['invoice_no']);

                $this->InvoiceModel->update($invoiceId, $invoiceData);
            }

            // Insert or update items
            if (!empty($input['items']) && is_array($input['items'])) {

                foreach ($input['items'] as $item) {

                    $itemData = [
                        'invoice_id'        => $invoiceId,
                        'policy_id'         => $item['policy_id'],
                        'policy_no'         => $item['policy_no'],
                        'commission_amount' => $item['commission_amount'] ?? 0,
                        'is_active'         => $item['is_active'] ?? 1,
                        'updated_at'        => date('Y-m-d H:i:s'),
                        'updated_by'        => $input['updated_by'] ?? 0
                    ];

                    // UPDATE (if id exists)
                    if (!empty($item['id'])) {

                        $this->InvoiceItemModel->update($item['id'], $itemData);

                    }else{  // INSERT (if id missing)

                        $itemData['created_at'] = date('Y-m-d H:i:s');
                        $itemData['created_by'] = $input['created_by'] ?? 0;
                        $this->InvoiceItemModel->insert($itemData);

                        //update pos_id to policy table
                        $this->PolicyModel->update($item['policy_id'], ['pos_id' => $posId ]);
                    }
                }
            }

            // UTR save — validations already passed above, just insert
            if ($isCreate && !empty($input['utrs']) && is_array($input['utrs'])) {

                foreach ($input['utrs'] as $utrRow) {

                    $utrNo = trim((string)($utrRow['utr_no'] ?? ''));
                    if ($utrNo === '') continue;

                    $utrDate = !empty($utrRow['utr_date'])
                        ? date('Y-m-d', strtotime($utrRow['utr_date']))
                        : null;

                    $amount = isset($utrRow['amount']) ? (float) $utrRow['amount'] : 0;

                    $result_utr = $this->InvoiceUtrModel->insert([
                        'invoice_id' => $invoiceId,
                        'utr_no'     => $utrNo,
                        'utr_date'   => $utrDate,
                        'amount'     => $amount,
                        'is_active'  => 1,
                        'created_at' => date('Y-m-d H:i:s'),
                        'created_by' => $input['created_by'] ?? 0,
                    ]);

                    if ($result_utr === false) {
                        $this->db->transRollback();
                        return $this->respond([
                            'status'  => 'failed',
                            'code'    => 500,
                            'message' => 'Could not save UTR details. Please try again.',
                        ], 500);
                    }
                }
            }

            if ($this->db->transStatus() === false) {
                $this->db->transRollback();
                throw new DataException("Transaction failed");
            }

            $this->db->transCommit();

            return $this->respond([
                'status' => 'success',
                'code'   => 200,
                'data'   => ['invoice_id' => $invoiceId]
            ], 200);

        } catch (\Exception $e) {
            $this->db->transRollback();
            return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
        }
    }

    private function generateInvoiceNo($pos_id)
    {
        $pos_id = !empty($pos_id) ? $pos_id : 0;
        $year = date('Y');

        // If pos_id is missing/invalid (0/null/"w"), generate invoice as MIG/Year/NNNNN
        // Otherwise generate as NIIB/{pos_id}/Year/NNNNN
        $prefix = ($pos_id > 0)
            ? "NIIB/{$pos_id}/{$year}/"
            : "MIG/{$year}/";

        // Get last invoice of current year
        $lastInvoice = $this->InvoiceModel
            ->select('invoice_no')
            ->like('invoice_no', $prefix, 'after')
            ->where('pos_id',$pos_id)
            ->orderBy('id', 'DESC')
            ->first();

        if ($lastInvoice && isset($lastInvoice['invoice_no'])) {
            // Extract numeric part
            $lastNumber = intval(substr($lastInvoice['invoice_no'], -5));
            $nextNumber = $lastNumber + 1;
        } else {
            $nextNumber = 1;
        }

        return $prefix . str_pad($nextNumber, 5, '0', STR_PAD_LEFT);
    }


    public function deleteInvoice()
    {
        try {
            $id = $this->request->getGet('id');

            if (!$id) {
                return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'ID is required'], 400);
            }

            $this->InvoiceModel->update($id, ['is_active' => 0]);
            $this->InvoiceItemModel->where('invoice_id', $id)->set(['is_active' => 0])->update();

            return $this->respond(['status' => 'success', 'code' => 200, 'data' => 'Invoice deleted'], 200);

        } catch (\Exception $e) {
            return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
        }
    }

    public function getCommissionRateList()
    {
        try {
            $input = $this->request->getJSON(true);
            if (! is_array($input)) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'JSON body is required',
                ], 400);
            }

            if (empty($input['manager_id'])) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'manager_id is required',
                ], 400);
            }

            $managerId = (int) $input['manager_id'];
            $invoiceIdRaw = $input['invoice_id'] ?? null;
            $invoiceId      = ($invoiceIdRaw !== null && $invoiceIdRaw !== '' && (int) $invoiceIdRaw > 0)
                ? (int) $invoiceIdRaw
                : 0;

            // Unused policies: no active invoice line on an active invoice (same as getAgentUnusedCommissionList).
            // Do not LEFT JOIN all invoice_items — inactive rows duplicate policies and break "unused" detection.
            if ($invoiceId === 0) {
                $query = $this->PolicyModel
                    ->select(
                        'partner_policy.policy_number as policy_no, partner_policy.id as policy_id, partner_policy.issued_date, '
                        . 'partner_policy.commission_amount, partner_policy.insured_name as customer_name, pe.agent_id, '
                        . 'pa.name as agent_name, pa.agent_code, partner_policy.premium_amount, '
                        . "NULL AS invoice_id, NULL AS invoice_no, '' AS utr_no",
                        false
                    )
                    ->join('partner_enquiry pe', 'pe.id = partner_policy.enquiry_id', 'left')
                    ->join('partner_quotation pq', 'pq.id = partner_policy.quotation_id', 'left')
                    ->join('partner_agent pa', 'pa.id = pe.agent_id', 'left')
                    ->where('partner_policy.is_active', 1)
                    // ->where('partner_policy.commission_amount >', 0)
                    // ->where('partner_policy.is_data_accuracy_checked', 1)
                    ->where('partner_policy.manager_id', $managerId)
                    ->where('partner_policy.policy_number IS NOT NULL', null, false)
                    ->where(
                        'NOT EXISTS (SELECT 1 FROM partner_invoice_items pii2 '
                        . 'INNER JOIN partner_invoice pi2 ON pi2.id = pii2.invoice_id AND pi2.is_active = 1 '
                        . 'WHERE pii2.policy_id = partner_policy.id AND pii2.is_active = 1)',
                        null,
                        false
                    );
            } else {
                $query = $this->PolicyModel
                    ->select(
                        'partner_policy.policy_number as policy_no, partner_policy.id as policy_id, partner_policy.issued_date, '
                        . 'partner_policy.commission_amount, partner_policy.insured_name as customer_name, pe.agent_id, '
                        . 'pa.name as agent_name, pa.agent_code, partner_policy.premium_amount, pi.id as invoice_id, pi.invoice_no, '
                        . 'COALESCE(('
                        . 'SELECT GROUP_CONCAT(piu.utr_no ORDER BY piu.id SEPARATOR ", ") '
                        . 'FROM partner_invoice_utr piu '
                        . 'WHERE piu.invoice_id = pi.id AND piu.is_active = 1'
                        . "), '') as utr_no",
                        false
                    )
                    ->join('partner_enquiry pe', 'pe.id = partner_policy.enquiry_id', 'left')
                    ->join('partner_quotation pq', 'pq.id = partner_policy.quotation_id', 'left')
                    ->join('partner_agent pa', 'pa.id = pe.agent_id', 'left')
                    ->join(
                        'partner_invoice_items pii',
                        'pii.policy_id = partner_policy.id AND pii.is_active = 1 AND pii.invoice_id = ' . $invoiceId,
                        'inner'
                    )
                    ->join('partner_invoice pi', 'pi.id = pii.invoice_id AND pi.is_active = 1', 'inner')
                    ->where('partner_policy.is_active', 1)
                    ->where('partner_policy.commission_amount >', 0)
                    ->where('partner_policy.is_data_accuracy_checked', 1)
                    ->where('partner_policy.manager_id', $managerId)
                    ->where('partner_policy.policy_number IS NOT NULL', null, false)
                    ->where('pi.id', $invoiceId)
                    ->distinct();
            }

            $this->applyCommissionRateListPayloadFilters($query, $input);

            $query->orderBy('partner_policy.issued_date', 'DESC');
            $query->orderBy('partner_policy.id', 'DESC');

            $data = $query->findAll();

            $total_commission = 0;
            foreach ($data as $policy) {
                $total_commission += (float) ($policy['commission_amount'] ?? 0);
            }

            return $this->respond([
                'status'           => 'success',
                'code'             => 200,
                'data'             => $data,
                'total_commission' => $total_commission,
                'total_policies'   => count($data),
            ], 200);
        } catch (\Exception $e) {
            return $this->respond([
                'status' => 'failed',
                'code'   => 500,
                'message'=> $e->getMessage(),
            ], 500);
        }
    }

    /**
     * Optional filters for getCommissionRateList JSON: agent_id, from_date, to_date, pos_id.
     */
    private function applyCommissionRateListPayloadFilters($query, array $input): void
    {
        if (! empty($input['agent_id'])) {
            if (is_array($input['agent_id'])) {
                $query->whereIn('pe.agent_id', $input['agent_id']);
            } else {
                $query->where('pe.agent_id', $input['agent_id']);
            }
        }
        if (! empty($input['from_date'])) {
            $query->where('partner_policy.issued_date >=', date('Y-m-d', strtotime((string) $input['from_date'])));
        }
        if (! empty($input['to_date'])) {
            $query->where('partner_policy.issued_date <=', date('Y-m-d', strtotime((string) $input['to_date'])));
        }
        if (! empty($input['pos_id'])) {
            $query->where('partner_policy.pos_id', $input['pos_id']);
        }
    }

    public function getAgentUnusedCommissionList()
    {
        try {

            $manager_id = $this->request->getGet('manager_id');
            if ($manager_id === null || $manager_id === '') {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'manager_id is required',
                ], 400);
            }

            // Unused = policy has no active invoice line on an active invoice (same idea as commissionPayoutReport pending).
            // NOT EXISTS avoids false "invoiced" when only inactive items/invoices exist, and avoids join fan-out.
            $query = $this->PolicyModel
                ->select([
                    'pe.agent_id',
                    'MAX(pa.name) as agent_name',
                    'MAX(pa.agent_code) as agent_code',
                    'SUM(partner_policy.commission_amount) as unused_commission_amount',
                    'COUNT(partner_policy.id) as total_policies',
                ])
                ->join('partner_enquiry pe', 'pe.id = partner_policy.enquiry_id', 'left')
                ->join('partner_agent pa', 'pa.id = pe.agent_id', 'left')
                ->where('partner_policy.is_active', 1)
                // ->where('partner_policy.commission_amount >', 0)
                // ->where('partner_policy.is_data_accuracy_checked', 1)
                ->where('partner_policy.manager_id', (int) $manager_id)
                ->where('partner_policy.policy_number IS NOT NULL', null, false)
                ->where('pe.agent_id IS NOT NULL', null, false)
                ->where(
                    'NOT EXISTS (SELECT 1 FROM partner_invoice_items pii2 '
                    . 'INNER JOIN partner_invoice pi2 ON pi2.id = pii2.invoice_id AND pi2.is_active = 1 '
                    . 'WHERE pii2.policy_id = partner_policy.id AND pii2.is_active = 1)',
                    null,
                    false
                )
                ->groupBy('pe.agent_id');

            $data = $query->findAll();

            // Grand total (optional but useful for FE)
            $grand_total = 0;
            foreach ($data as $row) {
                $grand_total += (float)$row['unused_commission_amount'];
            }

            return $this->respond([
                'status' => 'success',
                'code'   => 200,
                'data'   => $data,
                'grand_total_unused_commission' => $grand_total,
                'total_agents' => count($data)
            ], 200);

        } catch (\Exception $e) {

            return $this->respond([
                'status' => 'failed',
                'code'   => 500,
                'message'=> $e->getMessage()
            ], 500);
        }
    }

    public function updateUtrDetails()
    {
        $this->db->transBegin();

        try {
            $input = $this->request->getJSON(true);

            $invoiceId = (int)($input['invoice_id'] ?? 0);
            $paidAmount = (float)($input['paid_amount'] ?? 0);
            $paidDateRaw = $input['paid_date'] ?? null;
            $utrId = (int)($input['utr_id'] ?? 0);
            $utrNoInput = trim((string)($input['utr_no'] ?? ''));

            if ($invoiceId <= 0 || $paidAmount < 0 || empty($paidDateRaw)) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'invoice_id, paid_amount and paid_date are required'
                ], 400);
            }

            $invoice = $this->InvoiceModel
                ->where('id', $invoiceId)
                ->where('is_active', 1)
                ->first();

            if (empty($invoice)) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 404,
                    'message'=> 'Invoice not found'
                ], 404);
            }

            $paidDateTs = strtotime($paidDateRaw);
            if ($paidDateTs === false) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'Invalid paid_date format'
                ], 400);
            }
            $paidDate = date('Y-m-d', $paidDateTs);

            $utrPaidAmount = (float) (
                $this->InvoiceUtrModel
                    ->selectSum('amount', 'total')
                    ->where('invoice_id', $invoiceId)
                    ->where('is_active', 1)
                    ->first()['total'] ?? 0
            );

            $currentTotalPaid = $utrPaidAmount;
            $invoiceAmount = (float) ($invoice['invoice_amount'] ?? 0);
            $remainingBalance = max($invoiceAmount - $currentTotalPaid, 0);

            /*
             * If utr_id is empty/0 from request:
             * 1) create a fresh UTR row with given utr_no (or generated fallback),
             * 2) keep amount = 0 first,
             * 3) then continue existing update flow using the new utr_id.
             */
            if ($utrId <= 0) {
                $utrNo = $utrNoInput !== '' ? $utrNoInput : ('UTR' . date('YmdHis'));
                while (
                    !empty(
                        $this->InvoiceUtrModel
                            ->where('invoice_id', $invoiceId)
                            ->where('utr_no', $utrNo)
                            ->where('is_active', 1)
                            ->first()
                    )
                ) {
                    usleep(200000);
                    $utrNo = 'UTR' . date('YmdHis');
                }

                $insertedUtrId = $this->InvoiceUtrModel->insert([
                    'invoice_id' => $invoiceId,
                    'utr_no'     => $utrNo,
                    'utr_date'   => null,
                    'amount'     => 0,
                    'is_active'  => 1,
                    'created_at' => date('Y-m-d H:i:s'),
                    'created_by' => (int)($input['created_by'] ?? 0),
                    'updated_at' => date('Y-m-d H:i:s'),
                    'updated_by' => (int)($input['updated_by'] ?? 0),
                ]);

                if ($insertedUtrId === false) {
                    $this->db->transRollback();
                    return $this->respond([
                        'status' => 'failed',
                        'code'   => 500,
                        'message'=> 'Failed to create UTR',
                        'error'  => $this->InvoiceUtrModel->errors()
                    ], 500);
                }

                $utrId = (int)$insertedUtrId;
            }

            $utrQuery = $this->InvoiceUtrModel
                ->where('invoice_id', $invoiceId)
                ->where('is_active', 1)
                ->where('id', $utrId);
            $utrRow = $utrQuery->first();

            if (empty($utrRow)) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 404,
                    'message'=> 'Selected UTR not found for this invoice'
                ], 404);
            }

            $existingUtrAmount = (float)($utrRow['amount'] ?? 0);
            if ($paidAmount > ($remainingBalance + $existingUtrAmount)) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'Paid amount exceeds invoice balance'
                ], 400);
            }
            if ($paidAmount < $existingUtrAmount) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'Paid amount cannot be less than existing paid amount'
                ], 400);
            }

            $otherUtrPaidAmount = (float) (
                $this->InvoiceUtrModel
                    ->selectSum('amount', 'total')
                    ->where('invoice_id', $invoiceId)
                    ->where('is_active', 1)
                    ->where('id !=', (int)$utrRow['id'])
                    ->first()['total'] ?? 0
            );
            $maxAllowedForSelectedUtr = max($invoiceAmount - $otherUtrPaidAmount, 0);
            if ($paidAmount > $maxAllowedForSelectedUtr) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'Paid amount exceeds invoice balance'
                ], 400);
            }

            $updated = $this->InvoiceUtrModel->update((int)$utrRow['id'], [
                'amount'     => $paidAmount,
                'utr_date'   => $paidDate,
                'updated_at' => date('Y-m-d H:i:s'),
                'updated_by' => (int)($input['updated_by'] ?? 0),
            ]);

            if ($updated === false) {
                $this->db->transRollback();
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 500,
                    'message'=> 'Failed to update UTR payment',
                    'error'  => $this->InvoiceUtrModel->errors()
                ], 500);
            }

            $latestTotalPaid = ($currentTotalPaid - $existingUtrAmount) + $paidAmount;
            $newBalance = max($invoiceAmount - $latestTotalPaid, 0);
            $payoutStatus = $newBalance == 0.0 ? 2 : 1;

            $this->InvoiceModel->update($invoiceId, [
                'payout_status' => $payoutStatus,
                'updated_at'    => date('Y-m-d H:i:s'),
                'updated_by'    => (int)($input['updated_by'] ?? 0),
            ]);

            if ($this->db->transStatus() === false) {
                $this->db->transRollback();
                throw new DataException('Transaction failed');
            }

            $this->db->transCommit();

            return $this->respond([
                'status' => 'success',
                'code'   => 200,
                'data'   => [
                    'utr_id'         => (int)$utrRow['id'],
                    'utr_no'         => (string)$utrRow['utr_no'],
                    'invoice_id'     => $invoiceId,
                    'paid_amount'    => $latestTotalPaid,
                    'balance_amount' => $newBalance
                ]
            ], 200);
        } catch (\Exception $e) {
            $this->db->transRollback();
            return $this->respond([
                'status' => 'failed',
                'code'   => 500,
                'message'=> $e->getMessage()
            ], 500);
        }
    }

    public function addInvoicePayment()
    {
        return $this->updateUtrDetails();
    }

    public function listUtrDetails()
    {
        try {
            $invoiceId = (int)($this->request->getGet('invoice_id') ?? 0);
            if ($invoiceId <= 0) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'invoice_id is required'
                ], 400);
            }

            $invoice = $this->InvoiceModel
                ->select('id, invoice_no, invoice_amount')
                ->where('id', $invoiceId)
                ->where('is_active', 1)
                ->first();

            if (empty($invoice)) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 404,
                    'message'=> 'Invoice not found'
                ], 404);
            }

            $history = $this->db->table('partner_invoice_utr piu')
                ->select('
                    piu.id,
                    piu.invoice_id,
                    pi.invoice_no,
                    pi.invoice_amount AS total_amount,
                    piu.amount AS paid_amount,
                    piu.utr_no,
                    piu.utr_date AS paid_date,
                    piu.created_at,
                    COALESCE(psu.name, psc.name, "-") AS createdby_name,
                    (
                        pi.invoice_amount - COALESCE((
                            SELECT SUM(piu2.amount)
                            FROM partner_invoice_utr piu2
                            WHERE piu2.invoice_id = piu.invoice_id
                              AND piu2.is_active = 1
                              AND piu2.amount > 0
                              AND piu2.id <= piu.id
                        ), 0)
                    ) AS balance_amount
                ', false)
                ->join('partner_invoice pi', 'pi.id = piu.invoice_id', 'inner')
                ->join('partner_staff psc', 'psc.id = piu.created_by', 'left')
                ->join('partner_staff psu', 'psu.id = piu.updated_by', 'left')
                ->where('piu.invoice_id', $invoiceId)
                ->where('piu.is_active', 1)
                ->where('piu.amount >', 0)
                ->orderBy('piu.id', 'DESC')
                ->get()
                ->getResultArray();

            // Return unique UTR list for this invoice (remove duplicates by utr_no).
            // Keep only unpaid UTR rows (amount 0 or null).
            $availableUtrs = $this->db->table('partner_invoice_utr')
                ->select('MIN(id) as id, utr_no')
                ->where('invoice_id', $invoiceId)
                ->where('is_active', 1)
                ->where('(amount IS NULL OR amount <= 0)', null, false)
                ->where('TRIM(COALESCE(utr_no, "")) !=', '')
                ->groupBy('utr_no')
                ->orderBy('MIN(id)', 'ASC', false)
                ->get()
                ->getResultArray();

            $paidToDate = (float) (
                $this->InvoiceUtrModel
                    ->selectSum('amount', 'total')
                    ->where('invoice_id', $invoiceId)
                    ->where('is_active', 1)
                    ->first()['total'] ?? 0
            );
            $balanceAmount = max(((float)$invoice['invoice_amount']) - $paidToDate, 0);

            return $this->respond([
                'status' => 'success',
                'code'   => 200,
                'data'   => $history,
                'available_utrs' => $availableUtrs,
                'invoice'=> [
                    'invoice_id' => (int)$invoice['id'],
                    'invoice_no' => $invoice['invoice_no'],
                    'total_amount' => (float)$invoice['invoice_amount'],
                    'paid_to_date' => $paidToDate,
                    'balance_amount' => $balanceAmount,
                ],
            ], 200);
        } catch (\Exception $e) {
            return $this->respond([
                'status' => 'failed',
                'code'   => 500,
                'message'=> $e->getMessage()
            ], 500);
        }
    }

    public function addPaymentHistory()
    {
        return $this->listUtrDetails();
    }

    public function utrDetails()
    {
        try {
            $invoiceId = (int)($this->request->getGet('invoice_id') ?? 0);
            if ($invoiceId <= 0) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 400,
                    'message'=> 'invoice_id is required'
                ], 400);
            }

            $invoice = $this->InvoiceModel
                ->select('id, invoice_no, invoice_amount')
                ->where('id', $invoiceId)
                ->where('is_active', 1)
                ->first();

            if (empty($invoice)) {
                return $this->respond([
                    'status' => 'failed',
                    'code'   => 404,
                    'message'=> 'Invoice not found'
                ], 404);
            }

            $utrDetails = $this->InvoiceUtrModel
                ->select('id, invoice_id, amount as paid_amount, utr_no, utr_date')
                ->where('invoice_id', $invoiceId)
                ->where('is_active', 1)
                ->orderBy('id', 'ASC')
                ->findAll();

            $paidToDate = (float) (
                $this->InvoiceUtrModel
                    ->selectSum('amount', 'total')
                    ->where('invoice_id', $invoiceId)
                    ->where('is_active', 1)
                    ->first()['total'] ?? 0
            );

            $balanceAmount = max(((float)$invoice['invoice_amount']) - $paidToDate, 0);

            return $this->respond([
                'status' => 'success',
                'code'   => 200,
                'data'   => [
                    'utrs_details' => $utrDetails,
                    'invoice' => [
                        'invoice_id' => (int)$invoice['id'],
                        'invoice_no' => $invoice['invoice_no'],
                        'total_amount' => (float)$invoice['invoice_amount'],
                        'paid_to_date' => $paidToDate,
                        'balance_amount' => $balanceAmount,
                    ],
                ],
            ], 200);
        } catch (\Exception $e) {
            return $this->respond([
                'status' => 'failed',
                'code'   => 500,
                'message'=> $e->getMessage()
            ], 500);
        }
    }

    public function bulkUploadCommission()
    {
        $this->db->transBegin();
        try {
            $input = $this->request->getJSON(true);
            if (!is_array($input)) {
                $input = [];
            }

            $post = $this->request->getPost();
            if (!is_array($post)) {
                $post = [];
            }

            $rows = $input['rows'] ?? [];
            $updatedBy = (int)($input['updated_by'] ?? ($post['created_by'] ?? 0));

            // Multipart flow: read excel on backend and store upload metadata.
            $file = $this->request->getFile('file_name');
            if ($file && $file->isValid()) {
                $month = date('Y-m-d');
                $uploadPath = WRITEPATH . 'uploads/agent/incentive_file/';
                if (!is_dir($uploadPath)) {
                    mkdir($uploadPath, 0777, true);
                }

                $storedFileName = time() . '_' . $file->getRandomName();
                $file->move($uploadPath, $storedFileName);

                $this->AgentIncentiveFileModel->insert([
                    'incentive_month' => $month,
                    'incentive_file_name' => $storedFileName,
                    'file_type' => 'invoice',
                    'is_active' => 1,
                    'created_by' => $updatedBy > 0 ? $updatedBy : null,
                ], true);

                $spreadsheet = IOFactory::load($uploadPath . $storedFileName);
                $excelRows = $spreadsheet->getActiveSheet()->toArray(null, true, true, false);

                if (empty($excelRows)) {
                    return $this->respond([
                        'status' => 'failed',
                        'code' => 400,
                        'message' => 'Uploaded file is empty',
                    ], 400);
                }

                $headerRow = $excelRows[0] ?? [];
                $headerIndex = [];
                foreach ($headerRow as $idx => $headerValue) {
                    $normalized = preg_replace('/[^a-z0-9]/', '', strtolower(trim((string)$headerValue)));
                    if (!empty($normalized)) {
                        $headerIndex[$normalized] = (int)$idx;
                    }
                }

                $policyIdx = $headerIndex['policynumber'] ?? null;
                $invoiceIdx = $headerIndex['invoicenumber'] ?? null;
                $commissionIdx = $headerIndex['commission'] ?? ($headerIndex['commissionamount'] ?? null);

                if ($policyIdx === null || $commissionIdx === null) {
                    return $this->respond([
                        'status' => 'failed',
                        'code' => 400,
                        'message' => 'Expected columns: Policy Number, Invoice Number, Commission or Commission Amount',
                    ], 400);
                }

                $fallbackInvoiceNo = trim((string)($post['invoice_number'] ?? ''));
                $rows = [];
                foreach ($excelRows as $index => $row) {
                    if ($index === 0) {
                        continue;
                    }

                    $policyNo = trim((string)($row[$policyIdx] ?? ''));
                    $invoiceNoFromFile = $invoiceIdx !== null ? trim((string)($row[$invoiceIdx] ?? '')) : '';
                    $invoiceNo = $invoiceNoFromFile !== '' ? $invoiceNoFromFile : $fallbackInvoiceNo;
                    $commissionRaw = trim((string)($row[$commissionIdx] ?? ''));

                    if ($policyNo === '' && $invoiceNo === '' && $commissionRaw === '') {
                        continue;
                    }

                    $commission = (float)str_replace(',', '', $commissionRaw);
                    if ($policyNo === '' || $invoiceNo === '' || !is_numeric(str_replace(',', '', $commissionRaw))) {
                        return $this->respond([
                            'status' => 'failed',
                            'code' => 400,
                            'message' => 'Invalid data at line ' . ($index + 1),
                        ], 400);
                    }

                    $rows[] = [
                        'line_no' => $index + 1,
                        'policy_number' => $policyNo,
                        'invoice_no' => $invoiceNo,
                        'commission_amount' => $commission,
                    ];
                }
            }

            if (empty($rows) || !is_array($rows)) {
                return $this->respond([
                    'status' => 'failed',
                    'code' => 400,
                    'message' => 'rows is required',
                ], 400);
            }

            $mismatchRows = [];
            $validRows = [];
            $skippedRows = 0;

            foreach ($rows as $row) {
                $policyNo = trim((string)($row['policy_number'] ?? ''));
                $invoiceNo = trim((string)($row['invoice_no'] ?? ''));
                $commissionAmount = (float)($row['commission_amount'] ?? 0);
                $lineNo = (int)($row['line_no'] ?? 0);

                if ($policyNo === '' || $invoiceNo === '') {
                    $skippedRows++;
                    continue;
                }

                $record = $this->db->table('partner_invoice_items pii')
                    ->select('
                        pii.id as item_id,
                        pii.invoice_id,
                        pii.policy_id,
                        pii.policy_no,
                        pi.invoice_no,
                        pi.agent_id as invoice_agent_json,
                        pe.agent_id as policy_agent_id
                    ')
                    ->join('partner_invoice pi', 'pi.id = pii.invoice_id', 'inner')
                    ->join('partner_policy pp', 'pp.id = pii.policy_id', 'left')
                    ->join('partner_enquiry pe', 'pe.id = pp.enquiry_id', 'left')
                    ->where('pii.is_active', 1)
                    ->where('pi.is_active', 1)
                    ->where('pi.invoice_no', $invoiceNo)
                    ->where('pii.policy_no', $policyNo)
                    ->get()
                    ->getRowArray();

                if (empty($record)) {
                    $skippedRows++;
                    continue;
                }

                $invoiceAgentIds = $this->extractAgentIdsFromInvoice($record['invoice_agent_json'] ?? '');
                $policyAgentId = isset($record['policy_agent_id']) ? (int)$record['policy_agent_id'] : 0;
                $targetAgentId = !empty($invoiceAgentIds) ? (int)$invoiceAgentIds[0] : 0;
                $isMismatch = $policyAgentId > 0 && !in_array($policyAgentId, $invoiceAgentIds, true);

                if ($isMismatch) {
                    $mismatchRows[] = [
                        'line_no' => $lineNo,
                        'item_id' => (int)$record['item_id'],
                        'invoice_id' => (int)$record['invoice_id'],
                        'policy_id' => (int)$record['policy_id'],
                        'policy_number' => $policyNo,
                        'invoice_no' => $invoiceNo,
                        'commission_amount' => $commissionAmount,
                        'policy_agent_id' => $policyAgentId,
                        'target_agent_id' => $targetAgentId,
                    ];
                    continue;
                }

                $validRows[] = [
                    'item_id' => (int)$record['item_id'],
                    'invoice_id' => (int)$record['invoice_id'],
                    'policy_id' => (int)$record['policy_id'],
                    'commission_amount' => $commissionAmount,
                ];
            }

            if (!empty($mismatchRows)) {
                $this->ensureBulkUploadStagingTable();
                $token = $this->generateProceedToken();

                $this->db->table('invoice_bulk_upload_staging')->insert([
                    'proceed_token' => $token,
                    'payload_json' => json_encode([
                        'rows' => $rows,
                        'updated_by' => $updatedBy,
                    ]),
                    'mismatch_json' => json_encode($mismatchRows),
                    'created_at' => date('Y-m-d H:i:s'),
                    'created_by' => $updatedBy,
                    'is_active' => 1,
                ]);

                // Keep staged token/payload so proceed API can continue later.
                $this->db->transCommit();
                return $this->respond([
                    'status' => 'partner_mismatch',
                    'code' => 200,
                    'message' => 'Partner mismatch detected',
                    'mismatch_count' => count($mismatchRows),
                    'mismatches' => $mismatchRows,
                    'proceed_token' => $token,
                ], 200);
            }

            $updatedCount = $this->applyBulkCommissionRows($validRows, $updatedBy);

            if ($this->db->transStatus() === false) {
                $this->db->transRollback();
                throw new DataException('Transaction failed');
            }

            $this->db->transCommit();
            return $this->respond([
                'status' => 'success',
                'code' => 200,
                'message' => 'Bulk upload completed',
                'updated_count' => $updatedCount,
                'skipped_count' => $skippedRows,
            ], 200);
        } catch (\Exception $e) {
            $this->db->transRollback();
            return $this->respond([
                'status' => 'failed',
                'code' => 500,
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    public function bulkUploadCommissionProceed()
    {
        $this->db->transBegin();
        try {
            $input = $this->request->getJSON(true);
            $proceedToken = trim((string)($input['proceed_token'] ?? ''));
            $forceReassign = (int)($input['force_reassign_partner'] ?? 0);
            $updatedBy = (int)($input['updated_by'] ?? 0);

            if ($proceedToken === '' || $forceReassign !== 1) {
                return $this->respond([
                    'status' => 'failed',
                    'code' => 400,
                    'message' => 'proceed_token and force_reassign_partner=1 are required',
                ], 400);
            }

            $this->ensureBulkUploadStagingTable();
            $staging = $this->db->table('invoice_bulk_upload_staging')
                ->where('proceed_token', $proceedToken)
                ->where('is_active', 1)
                ->get()
                ->getRowArray();

            if (empty($staging)) {
                return $this->respond([
                    'status' => 'failed',
                    'code' => 404,
                    'message' => 'Invalid or expired proceed token',
                ], 404);
            }

            $payload = json_decode($staging['payload_json'] ?? '{}', true);
            $rows = $payload['rows'] ?? [];
            $mismatchRows = json_decode($staging['mismatch_json'] ?? '[]', true);

            foreach ($mismatchRows as $mismatch) {
                $policyId = (int)($mismatch['policy_id'] ?? 0);
                $targetAgentId = (int)($mismatch['target_agent_id'] ?? 0);
                if ($policyId <= 0 || $targetAgentId <= 0) {
                    continue;
                }

                $policy = $this->PolicyModel->select('enquiry_id')->where('id', $policyId)->first();
                if (!empty($policy['enquiry_id'])) {
                    $this->EnquiryModel->update((int)$policy['enquiry_id'], [
                        'agent_id' => $targetAgentId,
                        'updated_at' => date('Y-m-d H:i:s'),
                        'updated_by' => $updatedBy,
                    ]);
                }
            }

            $finalRows = [];
            foreach ($rows as $row) {
                $policyNo = trim((string)($row['policy_number'] ?? ''));
                $invoiceNo = trim((string)($row['invoice_no'] ?? ''));
                $commissionAmount = (float)($row['commission_amount'] ?? 0);
                if ($policyNo === '' || $invoiceNo === '') {
                    continue;
                }

                $record = $this->db->table('partner_invoice_items pii')
                    ->select('pii.id as item_id, pii.invoice_id, pii.policy_id')
                    ->join('partner_invoice pi', 'pi.id = pii.invoice_id', 'inner')
                    ->where('pii.is_active', 1)
                    ->where('pi.is_active', 1)
                    ->where('pi.invoice_no', $invoiceNo)
                    ->where('pii.policy_no', $policyNo)
                    ->get()
                    ->getRowArray();

                if (empty($record)) {
                    continue;
                }

                $finalRows[] = [
                    'item_id' => (int)$record['item_id'],
                    'invoice_id' => (int)$record['invoice_id'],
                    'policy_id' => (int)$record['policy_id'],
                    'commission_amount' => $commissionAmount,
                ];
            }

            $updatedCount = $this->applyBulkCommissionRows($finalRows, $updatedBy);

            $this->db->table('invoice_bulk_upload_staging')
                ->where('id', (int)$staging['id'])
                ->update([
                    'is_active' => 0,
                    'updated_at' => date('Y-m-d H:i:s'),
                    'updated_by' => $updatedBy,
                ]);

            if ($this->db->transStatus() === false) {
                $this->db->transRollback();
                throw new DataException('Transaction failed');
            }

            $this->db->transCommit();
            return $this->respond([
                'status' => 'success',
                'code' => 200,
                'message' => 'Bulk upload completed with partner reassignment',
                'updated_count' => $updatedCount,
            ], 200);
        } catch (\Exception $e) {
            $this->db->transRollback();
            return $this->respond([
                'status' => 'failed',
                'code' => 500,
                'message' => $e->getMessage(),
            ], 500);
        }
    }

    private function applyBulkCommissionRows(array $rows, int $updatedBy): int
    {
        $updatedCount = 0;
        foreach ($rows as $row) {
            $itemId = (int)($row['item_id'] ?? 0);
            $invoiceId = (int)($row['invoice_id'] ?? 0);
            $policyId = (int)($row['policy_id'] ?? 0);
            $commissionAmount = (float)($row['commission_amount'] ?? 0);
            if ($itemId <= 0) {
                continue;
            }

            $affected = $this->db->table('partner_invoice_items')
                ->where('id', $itemId)
                ->where('invoice_id', $invoiceId)
                ->where('policy_id', $policyId)
                ->where('is_active', 1)
                ->update([
                    'commission_amount' => $commissionAmount,
                    'updated_at' => date('Y-m-d H:i:s'),
                    'updated_by' => $updatedBy,
                ]);

            if ($affected) {
                $updatedCount++;
            }
        }

        return $updatedCount;
    }

    private function extractAgentIdsFromInvoice($agentJson): array
    {
        if (is_array($agentJson)) {
            return array_values(array_map('intval', $agentJson));
        }
        if ($agentJson === null || $agentJson === '') {
            return [];
        }

        $decoded = json_decode((string)$agentJson, true);
        if (is_array($decoded)) {
            return array_values(array_map('intval', $decoded));
        }

        if (is_numeric($agentJson)) {
            return [(int)$agentJson];
        }

        return [];
    }

    private function generateProceedToken(): string
    {
        return bin2hex(random_bytes(16));
    }

    private function ensureBulkUploadStagingTable(): void
    {
        $sql = "CREATE TABLE IF NOT EXISTS invoice_bulk_upload_staging (
            id INT AUTO_INCREMENT PRIMARY KEY,
            proceed_token VARCHAR(64) NOT NULL UNIQUE,
            payload_json LONGTEXT NULL,
            mismatch_json LONGTEXT NULL,
            is_active TINYINT(1) NOT NULL DEFAULT 1,
            created_at DATETIME NULL,
            created_by INT NULL,
            updated_at DATETIME NULL,
            updated_by INT NULL
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
        $this->db->query($sql);
    }


    // public function getCommissionRateList()
    // {
    //     try {

    //         $input = $this->request->getJSON(true);

    //         if (empty($input['broker_id']) || empty($input['issued_date'])) {
    //             return $this->respond([ 'status' => 'failed', 'code'   => 400, 'message'=> 'issued_date are required' ], 400);
    //         }

    //         $invoice_id = isset($input['invoice_id']) ? $input['invoice_id'] : '';


    //         $data_previous = $this->PolicyModel
    //             ->select('partner_policy.policy_number as policy_no,partner_policy.id as policy_id , partner_policy.issued_date, partner_policy.commission_amount, pe.name as customer_name , pq.premium_amount')
    //             ->join(
    //                 'partner_enquiry pe',
    //                 'pe.id = partner_policy.enquiry_id AND pe.broker_id = ' . (int)$input['broker_id'],
    //                 'left'
    //             )
    //             ->join('partner_quotation pq','pq.id = partner_policy.quotation_id ', 'left')
    //             // ->where('partner_policy.pos_id', $input['pos_id'])
    //             ->where('partner_policy.issued_date <=', date('Y-m-d', strtotime($input['issued_date'])))
    //             ->where('partner_policy.is_active', 1) 
    //             ->where('partner_policy.is_data_accuracy_checked', 1)
    //             ->findAll();

            
    //         $query = $this->PolicyModel
    //                 ->select('partner_policy.policy_number as policy_no, partner_policy.id as policy_id, partner_policy.issued_date, partner_policy.commission_amount, pe.name as customer_name, pq.premium_amount, pi.id as invoice_id, pi.invoice_no')
    //                 ->join(
    //                     'partner_enquiry pe',
    //                     'pe.id = partner_policy.enquiry_id AND pe.broker_id = ' . (int)$input['broker_id'],
    //                     'left'
    //                 )
    //                 ->join('partner_quotation pq','pq.id = partner_policy.quotation_id ', 'left')
    //                 ->join('partner_invoice_items pii', 'pii.policy_id = partner_policy.id', 'left' )
    //                 ->join('partner_invoice pi', 'pi.id = pii.invoice_id AND pi.is_active = 1', 'left')
    //                 // ->where('partner_policy.pos_id', $input['pos_id'])
    //                 ->where('partner_policy.issued_date <=', date('Y-m-d', strtotime($input['issued_date'])))
    //                 ->where('partner_policy.is_active', 1) 
    //                 ->where('partner_policy.commission_amount > 0') 
    //                 ->where('partner_policy.is_data_accuracy_checked', 1);
    //                 if (empty($invoice_id)) {
    //                     $query->where('pii.policy_id IS NULL');
    //                 } else {
    //                     $query->where('pi.id', $invoice_id);
    //                 }

    //                 $data = $query->findAll();

    //         return $this->respond([
    //             'status' => 'success',
    //             'code'   => 200,
    //             'previous'   => $data_previous,
    //             'data'   => $data
    //         ], 200);

    //     } catch (\Exception $e) {
    //         return $this->respond([
    //             'status' => 'failed',
    //             'code'   => 500,
    //             'message'=> $e->getMessage()
    //         ], 500);
    //     }
    // }



   



}
