<?php
namespace modules\hse\services;

use modules\hse\models\Exceedance;
use modules\hse\models\FatigueCalculator;
use modules\hse\models\RiskCalculator;
use modules\forms\models\Forms;
// Note: some projects already record exceedances in a form (dt058). If present we'll prefer that model.

/**
 * ComplianceChecker
 *
 * Simple runner to evaluate a roster (sequence of shifts) against Working Time limits
 * and record exceedances. This is intentionally lightweight and designed to be invoked
 * by a controller or a CLI task that iterates employees/rosters.
 */
class ComplianceChecker
{
    // Default WTR limits; can be overridden by constructor/config
    private $minRestHours = 12;          // minimum hours between booking off and booking on
    private $maxDutiesIn14Days = 13;     // max number of duties in any 14 day period
    private $maxHoursPerDuty = 12;       // max hours per duty

    private $fatigueCalc;
    private $riskCalc;

    public function __construct(array $config = [])
    {
        if (isset($config['minRestHours'])) $this->minRestHours = (int)$config['minRestHours'];
        if (isset($config['maxDutiesIn14Days'])) $this->maxDutiesIn14Days = (int)$config['maxDutiesIn14Days'];
        if (isset($config['maxHoursPerDuty'])) $this->maxHoursPerDuty = (int)$config['maxHoursPerDuty'];

        $this->fatigueCalc = new FatigueCalculator();
        $this->riskCalc = new RiskCalculator();
    }

    /**
     * Evaluate a roster (array of shifts). Each shift should be an array with keys:
     *  - 'person_id' (optional)
     *  - 'date' (YYYY-MM-DD)
     *  - 'start_time' (HH:MM)
     *  - 'end_time' (HH:MM)
     *  - 'hours_between_shifts' (float, optional)
     *  - 'work_intensity' (float, optional)
     *
     * Returns an array with per-shift results and any exceedances created.
     */
    public function checkRoster(array $shifts, $persist = true): array
    {
        $results = [];
        $exceedances = [];

        // Normalize shifts by date sort
        usort($shifts, function($a, $b){ return strcmp($a['date'] ?? '', $b['date'] ?? ''); });

        // Prepare sliding window for 14 day count
        $dates = array_map(function($s){ return $s['date'] ?? null; }, $shifts);

        foreach ($shifts as $index => $shift) {
            $duration = $this->calculateShiftDuration($shift['start_time'], $shift['end_time']);

            // 1) Min rest rule
            $hoursBetween = $shift['hours_between_shifts'] ?? null;
            if ($hoursBetween !== null && $hoursBetween < $this->minRestHours) {
                $ex = $this->recordExceedance($shift, $index, 'MIN_REST_HOURS', "hours_between={$hoursBetween}", $persist);
                $exceedances[] = $ex;
            }

            // 2) Max hours per duty
            if ($duration > $this->maxHoursPerDuty) {
                $ex = $this->recordExceedance($shift, $index, 'MAX_HOURS_PER_DUTY', "duration={$duration}", $persist);
                $exceedances[] = $ex;
            }

            // 3) Max duties in any 14 day window
            $count14 = $this->countDutiesInWindow($shifts, $shift['date'], 14);
            if ($count14 > $this->maxDutiesIn14Days) {
                $ex = $this->recordExceedance($shift, $index, 'MAX_DUTIES_14D', "count14={$count14}", $persist);
                $exceedances[] = $ex;
            }

            // 4) Fatigue & risk indices
            $fatigue = $this->fatigueCalc->calculateFatigueIndex($shift);
            $risk = $this->riskCalc->calculateRiskIndex($shift);

            $results[] = [
                'shift' => $shift,
                'duration' => $duration,
                'fatigue_index' => $fatigue,
                'risk_index' => $risk,
            ];
        }

        return ['results'=>$results, 'exceedances'=>$exceedances];
    }

    private function recordExceedance(array $shift, int $index, string $rule, string $value, bool $persist)
    {
        if ($persist) {
            // Prefer existing dt058 form model if available (some installations use this to record exceedances)
            // Preferred path: create a Forms wrapper so the DT058 virtual form is saved into forms table
            try {
                // Find dt058 form class
                $formClass = null;
                if (file_exists(\Yii::getAlias('@modules/forms/models/Dt058.php'))) {
                    $formClass = '\\modules\\forms\\models\\Dt058';
                } elseif (file_exists(\Yii::getAlias('@frontend/models/forms/Dt058.php'))) {
                    $formClass = '\\frontend\\models\\forms\\Dt058';
                }
                if ($formClass !== null) {
                    $formModel = new Forms(['formclass' => $formClass, 'id' => Forms::tempid(), 'language' => 'en']);
                    $formModel->form = 'dt058';
                    $data = $formModel->data;
                    // Map common fields: date, employee id/name, excess code and reason
                    if (is_object($data)) {
                        if (isset($shift['date']) && property_exists($data, 'date')) $data->date = $shift['date'];
                        // prefer numeric id fields
                        if (isset($shift['person_id'])) {
                            if (property_exists($data, 'employee1id')) $data->employee1id = $shift['person_id'];
                            elseif (property_exists($data, 'employeename1')) $data->employeename1 = $shift['person_id'];
                        }
                        if (property_exists($data, 'excesscode1')) $data->excesscode1 = $rule;
                        if (property_exists($data, 'reasoncode1')) $data->reasoncode1 = $value;
                        // attach full payload into data property if available
                        if (property_exists($data, 'data')) $data->data = json_encode(['rule'=>$rule,'value'=>$value,'shift'=>$shift,'created_at'=>time()]);
                        $formModel->data = $data;
                    }
                    if ($formModel->save()) {
                        return ['via' => 'dt058', 'id' => $formModel->id];
                    }
                }
            } catch (\Throwable $t) {
                // fallthrough to Exceedance model on any failure
            }

            $m = new Exceedance();
            $m->person_id = $shift['person_id'] ?? null;
            $m->shift_date = $shift['date'] ?? null;
            $m->shift_index = $index;
            $m->rule = $rule;
            $m->value = $value;
            $m->created_at = time();
            if (!$m->save()) {
                // Save failed; return array representation instead
                return ['error'=>'save_failed','errors'=>$m->errors,'shift'=>$shift,'rule'=>$rule,'value'=>$value];
            }
            return $m->attributes;
        }

        return ['person_id'=>$shift['person_id'] ?? null,'shift_date'=>$shift['date'] ?? null,'rule'=>$rule,'value'=>$value];
    }

    private function countDutiesInWindow(array $shifts, $date, int $days)
    {
        $startTs = strtotime($date);
        $fromTs = strtotime("-".($days-1)." days", $startTs);
        $count = 0;
        foreach ($shifts as $s) {
            $d = isset($s['date']) ? strtotime($s['date']) : null;
            if ($d !== false && $d !== null && $d >= $fromTs && $d <= $startTs) $count++;
        }
        return $count;
    }

    private function calculateShiftDuration($start, $end)
    {
        $s = strtotime($start);
        $e = strtotime($end);
        if ($e <= $s) $e += 24*3600;
        $hours = ($e - $s) / 3600.0;
        return round($hours, 2);
    }
}
