<?php

namespace App\Controllers\Admin;

use App\Controllers\BaseController;
use App\Models\ProductionModel;
use App\Models\OrderModel;
use App\Models\OrderItemModel;
use App\Models\ProductionQueueHistoryModel;
use App\Services\MonthlyPoScheduler;
use Config\Database;

class ProductionController extends BaseController
{
    protected ProductionModel $productionModel;
    protected OrderModel $orderModel;
    protected OrderItemModel $itemModel;
    protected ProductionQueueHistoryModel $queueHistoryModel;
    protected MonthlyPoScheduler $monthlyPoScheduler;

    public function __construct()
    {
        $this->productionModel = new ProductionModel();
        $this->orderModel      = new OrderModel();
        $this->itemModel       = new OrderItemModel();
        $this->queueHistoryModel = new ProductionQueueHistoryModel();
        $this->monthlyPoScheduler = new MonthlyPoScheduler();
    }

    public function index()
    {
        // Saat modul dibuka, buat header/antrian batch yang belum ada untuk
        // data lama maupun order yang baru saja dipecah saat diedit.
        $this->synchronizeScheduledProductions();

        $productions = $this->productionModel
            ->select('productions.*, orders.order_number, orders.customer_name, COALESCE(productions.scheduled_month, orders.target_date) AS target_date')
            ->join('orders', 'orders.id_order=productions.id_order')
            ->orderBy('productions.scheduled_month', 'ASC')
            ->orderBy('productions.id_production', 'ASC')
            ->findAll();

        $db = Database::connect();
        foreach ($productions as &$production) {
            $batchMonth = $this->batchMonth($production);
            $batchEnd = date('Y-m-t', strtotime($batchMonth));
            $plannedQuantity = $db->table('order_production_schedules')
                ->select('quantity')
                ->where('id_order', (int) $production['id_order'])
                ->where('scheduled_month', $batchMonth)
                ->get()
                ->getRowArray()['quantity'] ?? null;

            $production['batch_quantity'] = $plannedQuantity === null
                ? (int) ($db->table('production_queue')
                ->selectSum('qty_order', 'total_qty')
                ->where('id_order', (int) $production['id_order'])
                ->where('tanggal_produksi >=', $batchMonth)
                ->where('tanggal_produksi <=', $batchEnd)
                ->get()
                ->getRowArray()['total_qty'] ?? 0)
                : (int) $plannedQuantity;
        }
        unset($production);

        $data = [
            'title' => 'Produksi',
            'productions' => $productions,
        ];

        return view('admin/productions/index', $data);
    }

    /** Ensure every active PO has production headers and queues per batch. */
    private function synchronizeScheduledProductions(): void
    {
        $orders = $this->orderModel
            ->select('id_order')
            ->where('order_type', 'po')
            ->where('order_status', 'production')
            ->findAll();

        foreach ($orders as $order) {
            try {
                $this->createProductionFromOrder((int) $order['id_order']);
            } catch (\Throwable $e) {
                log_message('error', 'Sinkronisasi batch produksi gagal untuk order ' . $order['id_order'] . ': ' . $e->getMessage());
            }
        }
    }

    /**
     * Create production header for an order.
     * (Used when admin triggers manually)
     */
    public function create($orderId)
    {
        $order = $this->orderModel->find($orderId);
        if (!$order) {
            return redirect()->back();
        }

        $this->createProductionFromOrder((int) $orderId);

        return redirect()->to('/admin/production');
    }

    /**
     * Called after a PO order enters production.
     * Creates one production workflow for every scheduled monthly batch.
     */
    public function createProductionFromOrder(int $orderId, ?string $targetDate = null): void
    {
        $order = $this->orderModel->find($orderId);
        if (!$order || ($order['order_type'] ?? null) !== 'po') {
            return;
        }

        $items = $this->itemModel->where('id_order', $orderId)->findAll();
        $schedules = $this->monthlyPoScheduler->getOrderSchedule($orderId);
        if ($schedules === []) {
            $schedules = [[
                'scheduled_month' => $this->batchMonth(['scheduled_month' => $targetDate ?? ($order['target_date'] ?? null)]),
                'quantity' => array_sum(array_column($items, 'qty')),
            ]];
        }
        $db = Database::connect();
        $scheduledMonths = array_map(
            fn (array $schedule): string => $this->batchMonth(['scheduled_month' => $schedule['scheduled_month']]),
            $schedules
        );

        // Hapus hanya antrean/header yang masih waiting bila suatu perubahan
        // limit atau edit memindahkan batch ke bulan lain. Batch yang sudah
        // berjalan tidak disentuh.
        $existingHeaders = $this->productionModel
            ->where('id_order', $orderId)
            ->findAll();
        foreach ($existingHeaders as $header) {
            $headerMonth = $this->batchMonth($header);
            $headerEnd = date('Y-m-t', strtotime($headerMonth));
            $hasStartedQueue = $db->table('production_queue')
                ->where('id_order', $orderId)
                ->where('tanggal_produksi >=', $headerMonth)
                ->where('tanggal_produksi <=', $headerEnd)
                ->where('status !=', 'antrian')
                ->countAllResults() > 0;

            if (strtolower((string) ($header['status'] ?? 'waiting')) === 'waiting' && ! $hasStartedQueue) {
                $db->table('production_queue')
                    ->where('id_order', $orderId)
                    ->where('tanggal_produksi >=', $headerMonth)
                    ->where('tanggal_produksi <=', $headerEnd)
                    ->where('status', 'antrian')
                    ->delete();

                if (! in_array($headerMonth, $scheduledMonths, true)) {
                    $this->productionModel->delete((int) $header['id_production']);
                }
            }
        }

        // Setiap bulan memiliki header dan workflow produksi sendiri. Batch
        // mendatang tetap waiting sampai operator mulai memprosesnya.
        foreach ($schedules as $schedule) {
            $scheduledMonth = $this->batchMonth(['scheduled_month' => $schedule['scheduled_month']]);
            $existingHeader = $this->productionModel
                ->where('id_order', $orderId)
                ->where('scheduled_month', $scheduledMonth)
                ->first();

            if (! $existingHeader && ! $this->productionModel->insert([
                'id_order' => $orderId,
                'scheduled_month' => $scheduledMonth,
                'production_number' => $this->productionModel->generateProductionNumber(),
                'start_date' => null,
                'finish_date' => null,
                'status' => 'waiting',
                'progress' => 0,
            ])) {
                throw new \RuntimeException('Gagal membuat batch produksi.');
            }

            // Data lama pernah mengaktifkan seluruh order PO sekaligus.
            // Batch yang belum memasuki bulannya dikembalikan ke waiting.
            if ($existingHeader
                && $scheduledMonth > date('Y-m-01')
                && in_array(strtolower((string) ($existingHeader['status'] ?? 'waiting')), ['design', 'waiting'], true)
            ) {
                $this->productionModel->update((int) $existingHeader['id_production'], [
                    'status' => 'waiting',
                    'progress' => 0,
                    'start_date' => null,
                ]);
            }
        }

        $plannedItems = MonthlyPoScheduler::distributeItems(
            $items,
            $schedules,
            $targetDate ?? ($order['target_date'] ?? null)
        );
        $db->table('production_queue')
            ->where('id_order', $orderId)
            ->where('tanggal_produksi >', date('Y-m-t'))
            ->where('status', 'produksi')
            ->update(['status' => 'antrian']);

        foreach ($plannedItems as $plannedItem) {
            $item = $plannedItem['item'];
            $productId = (int) ($item['id_product'] ?? 0);
            $qty = (int) $plannedItem['quantity'];
            $scheduledDate = $plannedItem['scheduled_month'];

            if ($productId <= 0 || $qty <= 0) {
                continue;
            }

            // Hindari duplikasi item produksi jika method dipanggil ulang.
            $alreadyQueued = $this->productionQueueExists($orderId, $productId, $scheduledDate);
            if ($alreadyQueued) {
                continue;
            }

            $db->table('production_queue')->insert([
                'id_order' => $orderId,
                'id_product' => $productId,
                'qty_order' => $qty,
                'qty_hari_ini' => 0,
                'qty_sisa' => $qty,
                'tanggal_produksi' => $scheduledDate,
                'periode' => (int) date('Ym', strtotime($scheduledDate)),
                'status' => 'antrian',
            ]);
        }

        $this->syncOrderProductionStatus($orderId);
    }

    private function productionQueueExists(int $orderId, int $productId, string $scheduledDate): bool
    {
        $db = Database::connect();
        $row = $db->table('production_queue')
            ->select('id_queue')
            ->where('id_order', $orderId)
            ->where('id_product', $productId)
            ->where('tanggal_produksi', $scheduledDate)
            ->limit(1)
            ->get()
            ->getRowArray();

        return !empty($row);
    }


    public function show($id)
    {
        $production = $this->productionModel->find($id);
        if (!$production) {
            return redirect()->back();
        }

        $order = $this->orderModel->find($production['id_order']);
        $batchMonth = $this->batchMonth($production);
        $batchItems = array_filter(
            MonthlyPoScheduler::distributeItems(
                $this->itemModel->getItems($production['id_order']),
                $this->monthlyPoScheduler->getOrderSchedule((int) $production['id_order']),
                $order['target_date'] ?? null
            ),
            static fn (array $batch): bool => $batch['scheduled_month'] === $batchMonth
        );

        $items = array_map(static function (array $batch): array {
            $item = $batch['item'];
            $item['qty'] = $batch['quantity'];
            $item['subtotal'] = (float) ($item['price'] ?? 0) * $batch['quantity'];

            return $item;
        }, $batchItems);

        $data = [
            'title' => 'Detail Produksi',
            'production' => $production,
            'order' => $order,
            'items' => $items,
            'batchMonth' => $batchMonth,
        ];

        return view('admin/productions/show', $data);
    }

    /**
     * Operator advances the production header to its next workflow stage.
     */
    public function start($idProduction)
    {
        return $this->advance($idProduction);
    }

    /**
     * Advance the production workflow one valid database state at a time:
     * Advance according to the productions.status ENUM:
     * waiting -> design -> cutting -> assembly -> finishing -> qc -> completed.
     */
    public function advance($idProduction)
    {
        $production = $this->productionModel->find($idProduction);
        if (!$production) {
            return redirect()->back()->with('error', 'Data produksi tidak ditemukan.');
        }

        $steps = ['waiting', 'design', 'cutting', 'assembly', 'finishing', 'qc', 'completed'];
        $currentStatus = strtolower((string) ($production['status'] ?? 'waiting'));
        $currentIndex = array_search($currentStatus, $steps, true);
        if ($currentIndex === false) {
            $currentIndex = 0;
            $currentStatus = 'waiting';
        }
        if ($currentStatus === 'completed') {
            return redirect()->back()->with('error', 'Produksi sudah selesai.');
        }

        $batchMonth = $this->batchMonth($production);
        if ($batchMonth > date('Y-m-01')) {
            return redirect()->back()->with(
                'error',
                'Batch ini dijadwalkan pada ' . date('m/Y', strtotime($batchMonth)) . ' dan belum dapat diproses.'
            );
        }
        $batchEnd = date('Y-m-t', strtotime($batchMonth));

        $db = Database::connect();
        $db->transStart();

        $nextStatus = $steps[$currentIndex + 1];
        $queueStatus = $currentStatus === 'waiting' ? 'antrian' : 'produksi';
        $nextQueueStatus = $nextStatus === 'completed' ? 'selesai' : 'produksi';
        $queues = $db->table('production_queue')
            ->where('id_order', (int) $production['id_order'])
            ->where('tanggal_produksi >=', $batchMonth)
            ->where('tanggal_produksi <=', $batchEnd)
            ->where('status', $queueStatus)
            ->get()
            ->getResultArray();

        if ($queueStatus !== $nextQueueStatus) {
            foreach ($queues as $queue) {
                $queueUpdate = ['status' => $nextQueueStatus];
                if ($nextQueueStatus === 'selesai') {
                    $queueUpdate['qty_hari_ini'] = (int) ($queue['qty_order'] ?? 0);
                    $queueUpdate['qty_sisa'] = 0;
                }

                $db->table('production_queue')
                    ->where('id_queue', (int) $queue['id_queue'])
                    ->update($queueUpdate);
                $this->queueHistoryModel->saveHistory(
                    (int) $queue['id_queue'],
                    (string) $queue['status'],
                    $nextQueueStatus
                );
            }
        }

        $productionUpdate = [
            'status' => $nextStatus,
            'progress' => (int) round((($currentIndex + 1) / (count($steps) - 1)) * 100),
        ];
        if ($currentStatus === 'waiting') {
            $productionUpdate['start_date'] = $production['start_date'] ?: date('Y-m-d');
        }
        if ($nextStatus === 'completed') {
            $productionUpdate['finish_date'] = date('Y-m-d');
        }
        $this->productionModel->update($idProduction, $productionUpdate);
        $this->syncOrderProductionStatus((int) $production['id_order']);

        $db->transComplete();
        if ($db->transStatus() === false) {
            return redirect()->back()->with('error', 'Status produksi gagal diperbarui.');
        }

        return redirect()->back()->with('success', 'Status diperbarui: ' . $currentStatus . ' ke ' . $nextStatus . '.');
    }

    /**
     * Update progress for production header.
     * Used by route: production/update-progress/(:num)
     */
    public function updateProgress($idProduction)
    {
        $progress = (int) $this->request->getPost('progress');
        $status = (string) $this->request->getPost('status');

        $progress = max(0, min(100, $progress));
        $productionStatus = strtolower($status);
        if (!in_array($productionStatus, ['waiting', 'design', 'cutting', 'assembly', 'finishing', 'qc', 'completed', 'cancelled'], true)) {
            $productionStatus = 'waiting';
        }

        $data = [
            'progress' => $progress,
            'status' => $productionStatus,
        ];

        if ($productionStatus === 'completed') {
            $data['finish_date'] = date('Y-m-d');
            $data['progress'] = 100;
        }

        $this->productionModel->update($idProduction, $data);

        $production = $this->productionModel->find($idProduction);
        if (!$production) {
            return redirect()->back();
        }

        $this->syncOrderProductionStatus((int) $production['id_order']);

        return redirect()->back()->with('success', 'Progress diperbarui.');
    }

    /**
     * QC step for production header.
     * - result=qcrework => produksi -> rework
     * - result=qc => produksi -> selesai
     */
    public function qc($idProduction)
    {
        $production = $this->productionModel->find($idProduction);
        if (!$production) {
            return redirect()->back();
        }

        $result = (string) $this->request->getPost('result'); // qc|rework
        $isRework = $result === 'rework';

        $db = Database::connect();
        $idOrder = (int) $production['id_order'];
        $batchMonth = $this->batchMonth($production);
        $batchEnd = date('Y-m-t', strtotime($batchMonth));

        $queues = $db->table('production_queue')
            ->where('id_order', $idOrder)
            ->where('tanggal_produksi >=', $batchMonth)
            ->where('tanggal_produksi <=', $batchEnd)
            ->whereIn('status', ['produksi', 'antrian'])
            ->get()
            ->getResultArray();

        foreach ($queues as $q) {
            $before = $q['status'];

            if ($isRework) {
                $this->queueHistoryModel->saveHistory((int)$q['id_queue'], $before, 'rework');
                $db->table('production_queue')
                    ->where('id_queue', (int)$q['id_queue'])
                    ->update([
                        'status' => 'produksi',
                        'qty_sisa' => (int) ($q['qty_order'] ?? 0),
                        'qty_hari_ini' => 0,
                    ]);
            } else {
                $this->queueHistoryModel->saveHistory((int)$q['id_queue'], $before, 'qc');
                $db->table('production_queue')
                    ->where('id_queue', (int)$q['id_queue'])
                    ->update([
                        'status' => 'selesai',
                        'qty_hari_ini' => (int) ($q['qty_order'] ?? 0),
                        'qty_sisa' => 0,
                    ]);
            }
        }

        if ($isRework) {
            $this->productionModel->update($idProduction, ['status' => 'design']);
            $this->syncOrderProductionStatus($idOrder);

            return redirect()->back()->with('success', 'QC gagal, produksi masuk rework.');
        }

        // If all queues finished, complete header
        $remaining = $db->table('production_queue')
            ->selectSum('qty_sisa')
            ->where('id_order', $idOrder)
            ->where('tanggal_produksi >=', $batchMonth)
            ->where('tanggal_produksi <=', $batchEnd)
            ->where('status !=', 'selesai')
            ->get()
            ->getRowArray();

        $remainingSum = (int) ($remaining['qty_sisa'] ?? 0);

        if ($remainingSum === 0) {
            $this->productionModel->update($idProduction, [
                'status' => 'completed',
                'finish_date' => date('Y-m-d'),
                'progress' => 100,
            ]);

            $this->syncOrderProductionStatus($idOrder);
        }

        return redirect()->back()->with('success', 'QC selesai. Produksi completed.');
    }

    public function delete($idProduction)
    {
        $production = $this->productionModel->find($idProduction);
        if ($production) {
            $this->productionModel->delete($idProduction);
            $this->syncOrderProductionStatus((int) $production['id_order']);
        }

        return redirect()->back()->with('success', 'Data produksi dihapus.');
    }

    private function batchMonth(array $production): string
    {
        $date = $production['scheduled_month'] ?? $production['target_date'] ?? null;
        $timestamp = $date ? strtotime((string) $date) : false;

        return date('Y-m-01', $timestamp === false ? time() : $timestamp);
    }

    /** Synchronize the commercial order only after considering every batch. */
    private function syncOrderProductionStatus(int $orderId): void
    {
        $batches = $this->productionModel
            ->where('id_order', $orderId)
            ->findAll();

        if ($batches === []) {
            return;
        }

        $statuses = array_map(
            static fn (array $batch): string => strtolower((string) ($batch['status'] ?? 'waiting')),
            $batches
        );
        $activeStatuses = array_values(array_filter(
            $statuses,
            static fn (string $status): bool => $status !== 'cancelled'
        ));

        if ($activeStatuses === []) {
            $this->orderModel->update($orderId, [
                'production_status' => $this->orderModel->normalizeProductionStatus('cancelled'),
                'order_status' => 'cancelled',
            ]);

            return;
        }

        $allCompleted = count(array_filter(
            $activeStatuses,
            static fn (string $status): bool => $status !== 'completed'
        )) === 0;
        $hasStarted = count(array_filter(
            $activeStatuses,
            static fn (string $status): bool => !in_array($status, ['waiting', 'completed'], true)
        )) > 0;

        $this->orderModel->update($orderId, [
            'production_status' => $this->orderModel->normalizeProductionStatus(
                $allCompleted ? 'completed' : ($hasStarted ? 'process' : 'waiting')
            ),
            'order_status' => $allCompleted ? 'completed' : 'production',
        ]);
    }
}

