<?php

namespace App\Services;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * ChatbotService (Advanced Mode)
 * ─────────────────────────────────────────────────────────────────────────────
 * Parses STRUCTURED user inputs via regex — no LLM involved.
 *
 * HOW TO ADD A NEW COMMAND:
 *   1. Add an entry to $handlers with a regex pattern and a handler method name.
 *   2. Write the private handler method (receives the captured group as $value).
 *   3. Add a usage example to unknownCommand() so users know about it.
 *
 * Pattern convention:  /keyword\s*=\s*(.+)/i
 *   - The first capture group is the raw value after "=".
 *   - The handler receives it already trimmed.
 */
class ChatbotService
{
    /**
     * Registry of structured-command handlers.
     *
     * Each entry:
     *   'pattern' — PCRE regex; capture group 1 is the extracted value.
     *   'handler' — name of the private method that processes the command.
     *
     * For two-value commands (e.g. invoice report), group 1 and group 2 are
     * both passed to the handler; single-value handlers simply ignore $value2.
     */
    private array $handlers = [
        [
            'pattern' => '/inv(?:oice)?(?:\s*(?:number|num|no\.?))?\s*(?:=|is| |equal\s+to)\s*([^\s,\.]+)/i',
            'handler' => 'handleInvoice',
        ],

        // ── Invoice report: item type lookup ──────────────────────────────────
        // Accepts patterns like:
        //   item = 674071 plant = Vahinie Unit 2
        //   item code = 674071 plant = Vahinie Unit 2
        //   check item 674071 for plant Vahinie Unit 2
        [
            'pattern' => '/item(?:\s*code)?\s*(?:=|is|:)?\s*([^\s,]+)\s+(?:for\s+)?plant\s*(?:=|is|:)?\s*(.+)/i',
            'handler' => 'handleInvoiceReport',
        ],

        // ── Add more commands here ────────────────────────────────────────────
        // Example:
        // [
        //     'pattern' => '/^\s*serial\s*=\s*(.+)/i',
        //     'handler' => 'handleSerial',
        // ],
    ];

    // ─────────────────────────────────────────────────────────────────────────
    //  Public entry point
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Dispatch the user's input to the matching handler.
     */
    public function ask(string $input): string
    {
        $input = trim($input);

        foreach ($this->handlers as $entry) {
            if (preg_match($entry['pattern'], $input, $matches)) {
                $value  = trim($matches[1] ?? '');
                $value2 = trim($matches[2] ?? '');
                return $this->{$entry['handler']}($value, $value2);
            }
        }

        return $this->unknownCommand($input);
    }

    // ─────────────────────────────────────────────────────────────────────────
    //  Handler: invoice = <invoice_number>
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Looks up scan status for an invoice number in invoice_validations.
     * Returns a plain-English string (used by the Advanced / free-text path).
     * Structured callers should use getInvoiceData() directly.
     */
    private function handleInvoice(string $invoiceNumber, string $_unused = ''): string
    {
        $data = $this->getInvoiceData($invoiceNumber);

        // For the plain-text path (advanced mode / ChatbotService::ask()),
        // reassemble a human-readable sentence from the structured data.
        if (in_array($data['type'], ['invalid', 'error', 'not_found'], true)) {
            return $data['message'];
        }

        if ($data['type'] === 'all_scanned') {
            $n        = $data['total'];
            $itemWord = $n === 1 ? 'serial number' : 'serial numbers';
            return "For invoice number {$data['invoice_number']}, all {$n} {$itemWord} "
                 . ($n === 1 ? 'has' : 'have') . ' been scanned. ✅';
        }

        // partial or none_scanned
        $total    = $data['total'];
        $scanned  = $data['scanned'];
        $notScan  = $data['not_scanned'];
        $inv      = $data['invoice_number'];
        $itemWord = $total === 1 ? 'serial number' : 'serial numbers';

        if ($scanned === 0) {
            $msg = "For invoice number {$inv}, there "
                 . ($total === 1 ? 'is' : 'are') . " {$total} {$itemWord} "
                 . 'and none have been scanned.';
        } else {
            $msg = "For invoice number {$inv}, there "
                 . ($total === 1 ? 'is' : 'are') . " {$total} {$itemWord} in total. "
                 . "Out of which {$scanned} "
                 . ($scanned === 1 ? 'has' : 'have') . ' been scanned and '
                 . "{$notScan} "
                 . ($notScan === 1 ? 'has' : 'have') . ' not been scanned.';
        }

        if (! empty($data['unscanned_serials'])) {
            $msg .= ' Unscanned serial numbers are: '
                  . implode(', ', $data['unscanned_serials']) . '.';
        }

        return $msg;
    }

    // ─────────────────────────────────────────────────────────────────────────
    //  Public structured accessor — used by ChatBot (Basic mode)
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Returns structured scan-status data for an invoice number.
     *
     * Return shape:
     * [
     *   'type'              => 'all_scanned' | 'partial' | 'none_scanned'
     *                        | 'not_found'   | 'error'   | 'invalid',
     *   'message'           => string,   // one-line human summary (no serial list)
     *   'invoice_number'    => string,
     *   'total'             => int,
     *   'scanned'           => int,
     *   'not_scanned'       => int,
     *   'unscanned_serials' => string[], // full list — may be large
     * ]
     */
    public function getInvoiceData(string $invoiceNumber): array
    {
        $invoiceNumber = preg_replace('/\s+/', '', $invoiceNumber);

        if (empty($invoiceNumber)) {
            return [
                'type'              => 'invalid',
                'message'           => 'Please provide a valid invoice number. Example: invoice = 3RA0013333',
                'invoice_number'    => '',
                'total'             => 0,
                'scanned'           => 0,
                'not_scanned'       => 0,
                'unscanned_serials' => [],
            ];
        }

        try {
            $rows = DB::select("
                SELECT
                    COALESCE(scanned_status, 'not scanned') AS status,
                    COUNT(*) AS total_count,
                    STRING_AGG(
                        CASE
                            WHEN scanned_status IS NULL THEN serial_number::text
                        END,
                        ', '
                    ) AS serial_numbers_not_scanned
                FROM invoice_validations
                WHERE invoice_number = ?
                GROUP BY scanned_status
            ", [$invoiceNumber]);
        } catch (\Exception $e) {
            Log::error('ChatbotService: invoice query failed', [
                'invoice' => $invoiceNumber,
                'error'   => $e->getMessage(),
            ]);

            return [
                'type'              => 'error',
                'message'           => "Sorry, I couldn't fetch data for invoice {$invoiceNumber}. "
                                     . 'Please try again or contact support if this keeps happening.',
                'invoice_number'    => $invoiceNumber,
                'total'             => 0,
                'scanned'           => 0,
                'not_scanned'       => 0,
                'unscanned_serials' => [],
            ];
        }

        if (empty($rows)) {
            return [
                'type'              => 'not_found',
                'message'           => "No records found for invoice number {$invoiceNumber}. "
                                     . 'Please double-check the invoice number and try again.',
                'invoice_number'    => $invoiceNumber,
                'total'             => 0,
                'scanned'           => 0,
                'not_scanned'       => 0,
                'unscanned_serials' => [],
            ];
        }

        // ── Aggregate rows ────────────────────────────────────────────────────
        $totalScanned    = 0;
        $totalNotScanned = 0;
        $unscannedSerials = [];

        foreach ($rows as $row) {
            if ($row->status === 'not scanned') {
                $totalNotScanned = (int) $row->total_count;
                if (! empty($row->serial_numbers_not_scanned)) {
                    $unscannedSerials = array_values(
                        array_filter(
                            array_map('trim', explode(',', $row->serial_numbers_not_scanned))
                        )
                    );
                }
            } else {
                $totalScanned += (int) $row->total_count;
            }
        }

        $grandTotal = $totalScanned + $totalNotScanned;

        // ── All scanned ───────────────────────────────────────────────────────
        if ($totalNotScanned === 0) {
            $n        = $grandTotal;
            $itemWord = $n === 1 ? 'serial number' : 'serial numbers';
            return [
                'type'              => 'all_scanned',
                'message'           => "All {$n} {$itemWord} scanned for invoice {$invoiceNumber}. ✅",
                'invoice_number'    => $invoiceNumber,
                'total'             => $grandTotal,
                'scanned'           => $totalScanned,
                'not_scanned'       => 0,
                'unscanned_serials' => [],
            ];
        }

        // ── None / partial scanned ────────────────────────────────────────────
        $type     = $totalScanned === 0 ? 'none_scanned' : 'partial';
        $itemWord = $grandTotal === 1 ? 'serial number' : 'serial numbers';

        if ($totalScanned === 0) {
            $summary = "Invoice {$invoiceNumber} — {$grandTotal} {$itemWord}, none scanned yet.";
        } else {
            $summary = "Invoice {$invoiceNumber} — {$grandTotal} {$itemWord} total: "
                     . "{$totalScanned} scanned, {$totalNotScanned} not scanned.";
        }

        return [
            'type'              => $type,
            'message'           => $summary,
            'invoice_number'    => $invoiceNumber,
            'total'             => $grandTotal,
            'scanned'           => $totalScanned,
            'not_scanned'       => $totalNotScanned,
            'unscanned_serials' => $unscannedSerials,
        ];
    }

    // ─────────────────────────────────────────────────────────────────────────
    //  Handler: item = <item_code> plant = <plant_name>
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Determines whether an item is a serial invoice or material invoice
     * for a given plant, using the sticker_masters table.
     *
     * @param  string  $itemCode   Extracted item code (capture group 1)
     * @param  string  $plantName  Extracted plant name (capture group 2)
     */
    private function handleInvoiceReport(string $itemCode, string $plantName): string
    {
        $itemCode  = trim($itemCode);
        $plantName = trim($plantName);

        if (empty($itemCode)) {
            return 'Please provide an item code. Example: item = 674071 plant = Vahinie Unit 2';
        }

        if (empty($plantName)) {
            return 'Please provide a plant name. Example: item = 674071 plant = Vahinie Unit 2';
        }

        try {
            $rows = DB::select("
                WITH plant_item AS (
                    SELECT ? AS user_plant,
                           ? AS user_item_code
                ),
                t1 AS (
                    SELECT
                        plants.id   AS plant_id,
                        plants.name AS plant_name,
                        ARRAY_AGG(items.code) AS item_codes
                    FROM plants
                    LEFT JOIN items ON plants.id = items.plant_id
                    GROUP BY plants.id, plants.name
                ),
                t2 AS (
                    SELECT
                        t1.plant_id,
                        t1.plant_name,
                        CASE
                            WHEN plant_item.user_item_code = ANY(t1.item_codes) THEN 1
                            ELSE 0
                        END AS exists_flag
                    FROM t1
                    CROSS JOIN plant_item
                    WHERE t1.plant_name = plant_item.user_plant
                ),
                t3 AS (
                    SELECT t2.plant_id, t2.plant_name, t2.exists_flag,
                           plant_item.user_item_code
                    FROM t2
                    LEFT JOIN plant_item ON plant_item.user_plant = t2.plant_name
                ),
                t4 AS (
                    SELECT items.id AS item_id,
                           t3.plant_id, t3.plant_name, t3.exists_flag, t3.user_item_code
                    FROM t3
                    LEFT JOIN items
                        ON t3.plant_id = items.plant_id
                       AND t3.user_item_code = items.code
                )
                SELECT
                    t4.item_id,
                    t4.plant_id,
                    t4.plant_name,
                    t4.exists_flag,
                    t4.user_item_code,
                    COALESCE(sticker_masters.material_type, 0) AS material_type,
                    CASE
                        WHEN sticker_masters.item_id IS NULL
                            THEN 'no match found'
                        WHEN COALESCE(sticker_masters.material_type, 0) = 0
                            THEN 'serial invoice'
                        ELSE 'material invoice'
                    END AS invoice_description
                FROM t4
                LEFT JOIN sticker_masters
                    ON sticker_masters.plant_id = t4.plant_id
                   AND sticker_masters.item_id  = t4.item_id
            ", [$plantName, $itemCode]);

        } catch (\Exception $e) {
            Log::error('ChatbotService: invoice report query failed', [
                'plant'     => $plantName,
                'item_code' => $itemCode,
                'error'     => $e->getMessage(),
            ]);

            return "Sorry, I couldn't fetch data for item {$itemCode} in plant {$plantName}. "
                 . 'Please try again or contact support.';
        }

        if (empty($rows)) {
            return "No data found for plant \"{$plantName}\". Please check the plant name and try again.";
        }

        $row = $rows[0];

        if ((int) $row->exists_flag === 0) {
            return 'Provided item code does not exist in the item table.';
        }

        return match ($row->invoice_description) {
            'no match found'  => "Item not found in sticker master for the plant {$row->plant_name}.",
            'serial invoice'  => 'It is a serial invoice item.',
            'material invoice' => 'It is a material invoice item.',
            default           => 'Unexpected result. Please contact support.',
        };
    }

    // ─────────────────────────────────────────────────────────────────────────
    //  Fallback for unrecognised input
    // ─────────────────────────────────────────────────────────────────────────

    private function unknownCommand(string $input): string
    {
        return "I didn't recognise that command. Please include a supported keyword with a value after '='.\n\n"
             . "• Invoice scan status:\n"
             . "  invoice = 3RA0013333\n"
             . "  what is the status of invoice = 3RA0013333\n\n"
             . "• Invoice type lookup:\n"
             . "  item = 674071 plant = Vahinie Unit 2\n"
             . "  item code = 674071 plant = Vahinie Unit 2\n\n"
             . 'Any sentence containing  keyword = value  will work.';
    }
}
