<?php

namespace App\Services;

use App\Models\RouteCard;
use App\Models\RouteCardProcess;
use App\Models\Machines;
use App\Models\EmployeeDetails;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;

class PlanningService
{
    protected $officeStart = '09:00';
    protected $officeEnd = '18:30';

    /**
     * Recalculate the entire production schedule based on Priority.
     * High Priority -> Medium -> Low
     * Within Priority -> Project Start Date -> Route Date
     */
    public function recalculateSchedule()
    {
        Log::info("Starting Global Reschedule via PlanningService");

        // 1. Fetch all APPROVABLE COMPLETED plans (Status = Planned/Approved)
        // We only schedule Planned & Approved cards.
        $plans = RouteCard::with(['processes' => function ($q) {
                $q->orderBy('id'); // Ensure sequence
            }])
            ->where('status', 'Planned')
            ->whereNotNull('approved_at')
            // Priority Sort Key: High=1, Medium=2, Low=3
            ->orderByRaw("FIELD(priority, 'High', 'Medium', 'Low')")
            ->orderBy('project_start_date')
            ->orderBy('route_date')
            ->get();

        Log::info("Found " . $plans->count() . " active plans to reschedule.");

        // 2. Initialize Resource Timelines (Machine & Operator)
        // Format: [resource_id => Carbon instance of when it becomes free]
        $machineTimeline = [];
        $operatorTimeline = [];

        // 3. Iterate through Projects (Highest Priority First)
        foreach ($plans as $rc) {
            $projectStart = Carbon::parse($rc->project_start_date ?? $rc->route_date);
            Log::info("Scheduling RC: {$rc->route_no} (Priority: {$rc->priority}) starting from {$projectStart}");

            foreach ($rc->processes as $proc) {
                if (strtolower($proc->source) === 'outsourced') {
                    continue; // Outsourced doesn't consume internal resources
                }

                // A. Dependency Constraint
                // Start after Previous Process ends
                $dependencyStart = null;
                if ($proc->previous_process_id) {
                    $prev = $rc->processes->where('id', $proc->previous_process_id)->first();
                    if ($prev) {
                        $dependencyStart = Carbon::parse($prev->end_date);
                    }
                }
                
                // If no dependency, start at Project Start
                $proposedStart = $dependencyStart ?: $projectStart->copy();

                // B. Resource Constraints (Machine & Operator)
                // We must look for the earliest "gap" or just append to end of timeline?
                // Heuristic: Appending to end of current timeline for that resource. 
                // (More complex "gap finding" can be added later if needed)
                
                $machineAvailable = $proposedStart;
                if ($proc->machine && isset($machineTimeline[$proc->machine])) {
                    $machineAvailable = $machineTimeline[$proc->machine];
                }

                $operatorAvailable = $proposedStart;
                if ($proc->operator && isset($operatorTimeline[$proc->operator])) {
                    $operatorAvailable = $operatorTimeline[$proc->operator];
                }

                // The valid start is the LATEST of all constraints
                $start = max($proposedStart, $machineAvailable, $operatorAvailable);

                // C. Office Hours Constraint
                $start = $this->adjustToOfficeHours($start);

                // D. Calculate Duration & End Time
                $durationMinutes = ((int)$proc->cycle_hours * 60) + (int)$proc->cycle_minutes;
                [$realStart, $realEnd] = $this->scheduleWithinOfficeHours($start, $durationMinutes);

                // E. Save Process Schedule
                if (
                    $proc->start_date != $realStart->format('Y-m-d H:i') ||
                    $proc->end_date != $realEnd->format('Y-m-d H:i')
                ) {
                    $proc->start_date = $realStart->format('Y-m-d H:i');
                    $proc->end_date = $realEnd->format('Y-m-d H:i');
                    $proc->save();
                }

                // F. Update Resource Timelines
                // Now this resource is booked until realEnd
                if ($proc->machine) {
                    $machineTimeline[$proc->machine] = $realEnd->copy();
                }
                if ($proc->operator) {
                    $operatorTimeline[$proc->operator] = $realEnd->copy();
                }
            }
        }
        
        Log::info("Global Reschedule Completed.");
    }

    protected function adjustToOfficeHours(Carbon $start)
    {
        $officeStart = $start->copy()->setTimeFromTimeString($this->officeStart);
        $officeEnd = $start->copy()->setTimeFromTimeString($this->officeEnd);

        if ($start->lt($officeStart)) {
            return $officeStart;
        }
        if ($start->gte($officeEnd)) {
            // Move to next day start
            return $start->copy()->addDay()->setTimeFromTimeString($this->officeStart);
        }
        return $start;
    }

    protected function scheduleWithinOfficeHours(Carbon $start, $durationMinutes)
    {
        $current = $this->adjustToOfficeHours($start->copy());
        $minutesLeft = $durationMinutes;

        while ($minutesLeft > 0) {
            $officeEndToday = $current->copy()->setTimeFromTimeString($this->officeEnd);
            
            // Minutes available today
            $minutesThisDay = min($minutesLeft, $current->diffInMinutes($officeEndToday, false));
            
            if ($minutesThisDay <= 0) {
                // Determine if we are exactly at end of day, move to next
                $current = $current->copy()->addDay()->setTimeFromTimeString($this->officeStart);
                continue;
            }

            $current = $current->copy()->addMinutes($minutesThisDay);
            $minutesLeft -= $minutesThisDay;

            if ($minutesLeft > 0) {
                // Moved to end of day, jump to next morning
                $current = $current->copy()->addDay()->setTimeFromTimeString($this->officeStart);
            }
        }
        return [$start, $current]; // Start and End Carbon objects
    }
}
