<?php

namespace App\Controllers;

use CodeIgniter\RESTful\ResourceController;
use CodeIgniter\HTTP\ResponseInterface;

class InvoicesController extends ResourceController
{
    protected $modelName = 'App\Models\InvoiceModel';
    protected $invoiceTestModel;
    protected $patientModel;
    protected $testModel;
    protected $testItemModel;
    protected $reportDetailModel;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->invoiceTestModel = model('App\Models\InvoiceTestModel');
        $this->patientModel = model('App\Models\PatientModel');
        $this->testModel = model('App\Models\TestModel');
        $this->testItemModel = model('App\Models\TestItemModel');
        $this->reportDetailModel = model('App\Models\ReportDetailModel');
    }

    /**
     * Count the total number of invoices.
     *
     * @return ResponseInterface
     */
    public function count()
    {
        $total = $this->model->countAll();
        return $this->respond(['total' => $total]);
    }

    /**
     * Count the total number of invoice tests.
     *
     * @return ResponseInterface
     */
    public function countTests()
    {
        $totalTests = $this->invoiceTestModel->countAll();
        return $this->respond(['total_tests' => $totalTests]);
    }

    /**
     * Return an array of resource objects, themselves in array format.
     *
     * @return ResponseInterface
     */
    public function index()
    {
        $perPage = $this->request->getVar('limit') ?? 10;
        $offset  = $this->request->getVar('offset') ?? 1;
        $search = $this->request->getVar('search') ?? '';
        $search = trim($search);

        $invoices = [];
        if($search !== '') {
            $invoices = $this->model
                // search by lab number or patient name
                ->groupStart()
                    ->like('invoices.lab_number', $search)
                    ->orLike('patients.name', $search)
                ->groupEnd()
                ->select('invoices.*, patients.id as patient_id, patients.name as patient_name, patients.age as patient_age, patients.sex as patient_sex, patients.phone as patient_phone')
                ->join('patients', 'patients.id = invoices.patient_id')
                ->orderBy('invoices.id', 'DESC')
                ->paginate($perPage, 'default', $offset);
        } else {
            $invoices = $this->model
            ->select('invoices.*, patients.id as patient_id, patients.name as patient_name, patients.age as patient_age, patients.sex as patient_sex, patients.phone as patient_phone')
                ->join('patients', 'patients.id = invoices.patient_id')
                ->orderBy('invoices.id', 'DESC')
                ->paginate($perPage, 'default', $offset);
        }

        $response = [
            'data'  => $invoices,
            'pager' => $this->model->pager->getDetails()
        ];

        return $this->respond($response);
    }

    /**
     * Return the properties of a resource object.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function show($id = null)
    {
        if($id == null) {
            return $this->failNotFound('Invoice ID is required.');
        }

        $invoice = $this->model
        ->select('invoices.*, patients.id as patient_id, patients.name as patient_name, patients.age as patient_age, patients.sex as patient_sex, patients.phone as patient_phone')
            ->join('patients', 'patients.id = invoices.patient_id')
            ->where('invoices.id', $id)
            ->first();

        if ($invoice === null) {
            return $this->failNotFound('Invoice not found.');
        }

        $invoice['tests'] = $this->invoiceTestModel
            ->select('tests.id as test_id, tests.code, tests.name, invoice_tests.price')
            ->join('tests', 'tests.id = invoice_tests.test_id')
            ->where('invoice_tests.invoice_id', $id)
            ->findAll();

        return $this->respond($invoice);
    }

    /**
     * Return a new resource object, with default properties.
     *
     * @return ResponseInterface
     */
    public function new()
    {
        $patients = $this->patientModel
            ->select('id, name')
            ->orderBy('name')
            ->findAll();

        $tests = $this->testModel
            ->select('id, name, code, price')
            ->orderBy('name')
            ->findAll();

        return $this->respond([
            'patients' => $patients,
            'tests'    => $tests,
        ]);
    }

    /**
     * Create a new resource object, from "posted" parameters.
     *
     * @return ResponseInterface
     */
    public function create()
    {
        $data = $this->request->getJSON(true);
        if (empty($data['patient_id']) || empty($data['tests'])) {
            return $this->fail('Patient ID and tests are required.');
        }

        $invoiceData = [
            'lab_number'        => $data['lab_number'] ?? null,
            'patient_id'        => $data['patient_id'] ?? null,
            'referred_by'       => $data['referred_by'] ?? null,
            'invoice_date'      => $data['invoice_date'] ?? null,
            'total_amount'      => $data['total_amount'] ?? null,
            'discount'          => $data['discount'] ?? null,
            'discounted_amount' => $data['discounted_amount'] ?? $data['total_amount'] ?? null,
            'paid_amount'       => $data['paid_amount'] ?? null,
            'balance'           => $data['balance'] ?? null,
            'collecting_date'   => $data['collecting_date'] ?? null,
        ];

        if (!isset($data['tests']) || !is_array($data['tests']) || empty($data['tests'])) {
            return $this->failValidationErrors(['tests' => 'At least one test is required.']);
        }

        if(!$this->model->insert($invoiceData)) {
            return $this->failValidationErrors($this->model->errors());
        }
        $invoiceId = $this->model->insertID();

        $testRows = [];
        foreach ($data['tests'] as $test) {
            if (!isset($test['test_id'], $test['price'])) {
                return $this->failValidationErrors(['tests' => 'Each test must have test_id and price.']);
            }

            $testRows[] = [
                'invoice_id' => $invoiceId,
                'test_id'    => $test['test_id'],
                'price'      => $test['price'],
                'status'     => 'Pending'
            ];
        }

        if (!$this->invoiceTestModel->insertBatch($testRows)) {
            return $this->failServerError('Failed to save invoice tests.');
        }

        return $this->respondCreated(['id' => $invoiceId], 'Invoice created successfully.');
    }

    /**
     * Return the editable properties of a resource object.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function edit($id = null)
    {
        if ($id == null) {
            return $this->failNotFound('Invoice ID is required.');
        }

        $invoice = $this->model
            ->select('invoices.*, patients.id as patient_id, patients.name as patient_name, patients.age as patient_age, patients.sex as patient_sex, patients.phone as patient_phone')
            ->join('patients', 'patients.id = invoices.patient_id')
            ->where('invoices.id', $id)
            ->first();

        if ($invoice === null) {
            return $this->failNotFound('Invoice not found.');
        }

        $invoice['tests'] = $this->invoiceTestModel
            ->select('tests.code, tests.name, invoice_tests.price')
            ->join('tests', 'tests.id = invoice_tests.test_id')
            ->where('invoice_tests.invoice_id', $id)
            ->findAll();

        return $this->respond($invoice);
    }

    /**
     * Add or update a model resource, from "posted" properties.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function update($id = null)
    {
        if ($id === null) {
            return $this->failNotFound('Invoice ID is required.');
        }
    
        $invoice = $this->model->find($id);
        if (!$invoice) {
            return $this->failNotFound('Invoice not found.');
        }
    
        $data = $this->request->getJSON(true);
        $invoiceData = [
            'lab_number'        => $data['lab_number'] ?? $invoice['lab_number'],
            'patient_id'        => $data['patient_id'] ?? $invoice['patient_id'],
            'referred_by'       => $data['referred_by'] ?? $invoice['referred_by'],
            'invoice_date'      => $data['invoice_date'] ?? $invoice['invoice_date'],
            'total_amount'      => $data['total_amount'] ?? $invoice['total_amount'],
            'discount'          => $data['discount'] ?? $invoice['discount'],
            'discounted_amount' => $data['discounted_amount'] ?? $invoice['discounted_amount'],
            'paid_amount'       => $data['paid_amount'] ?? $invoice['paid_amount'],
            'balance'           => $data['balance'] ?? $invoice['balance'],
            'collecting_date'   => $data['collecting_date'] ?? $invoice['collecting_date'],
        ];
    
        if (!$this->model->update($id, $invoiceData)) {
            return $this->failValidationErrors($this->model->errors());
        }

        if(!isset($data['tests']) || !is_array($data['tests']) || empty($data['tests'])) {
            return $this->respond(
                ['id' => $id],
                'Invoice updated successfully.'
            );
        }
    
        $this->invoiceTestModel->where('invoice_id', $id)->delete();
    
        $tests = $data['tests'] ?? [];
    
        $testRows = [];
        foreach ($tests as $test) {
            if (!isset($test['test_id']) || !isset($test['price'])) {
                return $this->failValidationErrors('Each test must include test_id and price.');
            }
    
            $testRows[] = [
                'invoice_id' => $id,
                'test_id'    => $test['test_id'],
                'price'      => $test['price'],
                'status'     => 'Pending'
            ];
        }
        
        if (!empty($testRows) && !$this->invoiceTestModel->insertBatch($testRows)) {
            return $this->failServerError('Failed to save updated invoice tests.');
        }
    
        return $this->respondUpdated(['id' => $id], 'Invoice updated successfully.');
    }

    /**
     * Delete the designated resource object from the model.
     *
     * @param int|string|null $id
     *
     * @return ResponseInterface
     */
    public function delete($id = null)
    {
        if ($id == null) {
            return $this->failNotFound('Invoice ID is required.');
        }

        $invoice = $this->model->find($id);
        if (!$invoice) {
            return $this->failNotFound('Invoice not found.');
        }

        if (!$this->model->delete($id)) {
            return $this->failServerError('Failed to delete invoice.');
        }

        return $this->respondDeleted(['id' => $id], 'Invoice deleted successfully.');
    }

    public function getInvoiceByPatientId($patientId = null)
    {
        if ($patientId === null) {
            return $this->failNotFound('Patient ID is required.');
        }

        $patient = $this->patientModel->find($patientId);
        if (!$patient) {
            return $this->failNotFound('Patient not found.');
        }

        $invoices = $this->model
            ->select('invoices.*, patients.id as patient_id, patients.name as patient_name, patients.age as patient_age, patients.sex as patient_sex, patients.phone as patient_phone')
            ->join('patients', 'patients.id = invoices.patient_id')
            ->where('patients.id', $patientId)
            ->findAll();

        if (empty($invoices)) {
            return $this->failNotFound('No invoices found for this patient.');
        }

        foreach ($invoices as &$invoice) {
            $invoice['tests'] = $this->invoiceTestModel
                ->select('tests.code, tests.name, invoice_tests.price')
                ->join('tests', 'tests.id = invoice_tests.test_id')
                ->where('invoice_tests.invoice_id', $invoice['id'])
                ->findAll();
        }

        return $this->respond($invoices);
    }

    public function getInvoiceByLabNumber($labNumber = null)
    {
        if ($labNumber === null) {
            return $this->failNotFound('Lab number is required.');
        }

        $invoice = $this->model
            ->select('invoices.*, patients.id as patient_id, patients.name as patient_name, patients.age as patient_age, patients.sex as patient_sex, patients.phone as patient_phone')
            ->join('patients', 'patients.id = invoices.patient_id')
            ->where('invoices.lab_number', $labNumber)
            ->first();

        if (!$invoice) {
            return $this->failNotFound('Invoice not found.');
        }

        $invoice['tests'] = $this->invoiceTestModel
            ->select('tests.code, tests.name, invoice_tests.price')
            ->join('tests', 'tests.id = invoice_tests.test_id')
            ->where('invoice_tests.invoice_id', $invoice['id'])
            ->findAll();

        return $this->respond($invoice);
    }

    public function getInvoiceTestById($id = null)  
    {
        if ($id === null) {
            return $this->failNotFound('Invoice Test ID is required.');
        }

        $invoiceTest = $this->invoiceTestModel
        ->select('invoice_tests.*, invoices.id as invoice_id, invoices.lab_number as lab_number, invoices.referred_by as referred_by, patients.id as patient_id, patients.name as patient_name, patients.age as patient_age, patients.sex as patient_sex, patients.phone as patient_phone, tests.id as test_id, tests.code as test_code, tests.name as test_name')
        ->join('invoices', 'invoices.id = invoice_tests.invoice_id')
        ->join('tests', 'tests.id = invoice_tests.test_id')
        ->join('patients', 'patients.id = invoices.patient_id')
        ->first();
        if (!$invoiceTest) {
            return $this->failNotFound('Invoice Test not found.');
        }

        $testItems  = $this->testItemModel
            ->select('test_items.*')
            ->where('test_items.test_id', $invoiceTest['test_id'])
            ->findAll();

        $reportDetails = $this->reportDetailModel
            ->select('report_details.*')
            ->join('reports', 'reports.id = report_details.report_id')
            ->join('invoice_tests', 'invoice_tests.id = reports.invoice_test_id')
            ->where('invoice_tests.id', $id)
            ->findAll();

        $detailsByTestItemId = [];
        foreach ($reportDetails as $detail) {
            $detailsByTestItemId[$detail['test_item_id']] = $detail;
        }

        foreach ($testItems as &$item) {
            if (isset($detailsByTestItemId[$item['id']])) {
                $item['result'] = $detailsByTestItemId[$item['id']]['result'];
                $item['remarks'] = $detailsByTestItemId[$item['id']]['remarks'];
            } else {
                $item['result'] = null;
                $item['remarks'] = null;
            }
        }
        unset($item);

        $invoiceTest['test_items'] = $testItems;

        return $this->respond($invoiceTest);
    }

    public function getInvoicesTestsByStatus() 
    {
        $status = $this->request->getGet('status');
        if($status !== 'Pending' && $status != 'Completed' && $status != 'Cancelled') {
            return $this->fail('Invalid status. Only "Pending", "Cancelled" and "Completed" are allowed.');
        }

        $invoices = $this->invoiceTestModel
            ->select('invoice_tests.*, invoices.lab_number as lab_number, invoices.referred_by as referred_by, patients.id as patient_id, patients.name as patient_name, patients.age as patient_age, patients.sex as patient_sex, patients.phone as patient_phone, tests.id as test_id, tests.code as test_code, tests.name as test_name')
            ->join('invoices', 'invoices.id = invoice_tests.invoice_id')
            ->join('tests', 'tests.id = invoice_tests.test_id')
            ->join('patients', 'patients.id = invoices.patient_id')
            ->where('invoice_tests.status', $status)
            ->orderBy('invoices.lab_number', 'DESC')
            ->findAll();

        $testIds = array_unique(array_column($invoices, 'test_id'));
        if (empty($testIds)) {
            return $this->respond([]);
        }

        $testItems  = $this->testItemModel
            ->select('test_items.*')
            ->whereIn('test_items.test_id', $testIds)
            ->findAll();

        $testItemsByTestId = [];
        foreach ($testItems as $item) {
            $testItemsByTestId[$item['test_id']][] = $item;
        }

        foreach ($invoices as &$invoiceTest) {
            $tid = $invoiceTest['test_id'];
            $invoiceTest['test_items'] = $testItemsByTestId[$tid] ?? [];
        }
        unset($invoiceTest);

        return $this->respond($invoices);
    }
}