<?php

class HoursOfService extends BaseModel
{
    public const DUTY_STATUSES = ['off_duty', 'sleeper_berth', 'driving', 'on_duty'];
    public const SPECIAL_STATUSES = ['', 'personal_conveyance', 'yard_move'];
    public const RULE_SETS = ['us_property', 'canada_south', 'canada_north'];

    public function profileForDriver(int $driverId): array
    {
        $stmt = $this->db->prepare('SELECT p.*, d.name AS driver_name, d.carrier_id, c.company_name AS carrier_name
            FROM drivers d
            LEFT JOIN driver_hos_profiles p ON p.driver_id = d.id
            LEFT JOIN carriers c ON c.id = d.carrier_id
            WHERE d.id = ? LIMIT 1');
        $stmt->execute([$driverId]);
        $row = $stmt->fetch();
        if (!$row) {
            throw new RuntimeException('Driver was not found.');
        }

        return array_merge([
            'driver_id' => $driverId,
            'operating_region' => 'cross_border',
            'default_rule_set' => 'us_property',
            'us_cycle' => '70_8',
            'canada_cycle' => 'cycle_1',
            'home_terminal_name' => '',
            'home_terminal_address' => '',
            'home_terminal_timezone' => 'America/Chicago',
            'log_day_start_time' => '00:00:00',
            'eld_provider' => '',
            'eld_device_identifier' => '',
            'eld_exempt' => 0,
            'eld_exemption_reason' => '',
            'short_haul_enabled' => 0,
            'personal_conveyance_allowed' => 1,
            'yard_move_allowed' => 1,
            'active' => 1,
        ], $row);
    }

    public function saveProfile(int $driverId, array $data): void
    {
        $operatingRegion = in_array(($data['operating_region'] ?? ''), ['us', 'canada', 'cross_border'], true) ? $data['operating_region'] : 'cross_border';
        $defaultRule = in_array(($data['default_rule_set'] ?? ''), self::RULE_SETS, true) ? $data['default_rule_set'] : 'us_property';
        $usCycle = in_array(($data['us_cycle'] ?? ''), ['60_7', '70_8'], true) ? $data['us_cycle'] : '70_8';
        $canadaCycle = in_array(($data['canada_cycle'] ?? ''), ['cycle_1', 'cycle_2'], true) ? $data['canada_cycle'] : 'cycle_1';
        $timezone = trim((string) ($data['home_terminal_timezone'] ?? 'America/Chicago')) ?: 'America/Chicago';
        try {
            new DateTimeZone($timezone);
        } catch (Throwable $exception) {
            $timezone = 'America/Chicago';
        }

        $stmt = $this->db->prepare('INSERT INTO driver_hos_profiles
            (driver_id, operating_region, default_rule_set, us_cycle, canada_cycle, home_terminal_name, home_terminal_address, home_terminal_timezone, log_day_start_time, eld_provider, eld_device_identifier, eld_exempt, eld_exemption_reason, short_haul_enabled, personal_conveyance_allowed, yard_move_allowed, active)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE operating_region=VALUES(operating_region), default_rule_set=VALUES(default_rule_set), us_cycle=VALUES(us_cycle), canada_cycle=VALUES(canada_cycle), home_terminal_name=VALUES(home_terminal_name), home_terminal_address=VALUES(home_terminal_address), home_terminal_timezone=VALUES(home_terminal_timezone), log_day_start_time=VALUES(log_day_start_time), eld_provider=VALUES(eld_provider), eld_device_identifier=VALUES(eld_device_identifier), eld_exempt=VALUES(eld_exempt), eld_exemption_reason=VALUES(eld_exemption_reason), short_haul_enabled=VALUES(short_haul_enabled), personal_conveyance_allowed=VALUES(personal_conveyance_allowed), yard_move_allowed=VALUES(yard_move_allowed), active=VALUES(active)');
        $stmt->execute([
            $driverId,
            $operatingRegion,
            $defaultRule,
            $usCycle,
            $canadaCycle,
            trim((string) ($data['home_terminal_name'] ?? '')) ?: null,
            trim((string) ($data['home_terminal_address'] ?? '')) ?: null,
            $timezone,
            preg_match('/^([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/', (string) ($data['log_day_start_time'] ?? '')) ? (string) $data['log_day_start_time'] : '00:00:00',
            trim((string) ($data['eld_provider'] ?? '')) ?: null,
            trim((string) ($data['eld_device_identifier'] ?? '')) ?: null,
            !empty($data['eld_exempt']) ? 1 : 0,
            trim((string) ($data['eld_exemption_reason'] ?? '')) ?: null,
            !empty($data['short_haul_enabled']) ? 1 : 0,
            !empty($data['personal_conveyance_allowed']) ? 1 : 0,
            !empty($data['yard_move_allowed']) ? 1 : 0,
            !empty($data['active']) ? 1 : 0,
        ]);
    }

    public function overview(?int $carrierId = null): array
    {
        $sql = "SELECT d.id, d.name, d.status, d.carrier_id, c.company_name AS carrier_name,
                    p.default_rule_set, p.us_cycle, p.canada_cycle, p.home_terminal_timezone, p.eld_exempt,
                    e.duty_status, e.special_status, e.event_at, e.location_address
                FROM drivers d
                LEFT JOIN carriers c ON c.id=d.carrier_id
                LEFT JOIN driver_hos_profiles p ON p.driver_id=d.id
                LEFT JOIN hos_events e ON e.id=(SELECT e2.id FROM hos_events e2 WHERE e2.driver_id=d.id AND e2.record_status='active' ORDER BY e2.event_at DESC,e2.id DESC LIMIT 1)
                WHERE d.status!='inactive'";
        $params = [];
        if ($carrierId) { $sql .= ' AND d.carrier_id=?'; $params[] = $carrierId; }
        $sql .= ' ORDER BY d.name';
        $stmt = $this->db->prepare($sql); $stmt->execute($params); $drivers = $stmt->fetchAll();

        foreach ($drivers as &$driver) {
            try {
                $driver['clocks'] = $this->clockSummary((int) $driver['id']);
            } catch (Throwable $exception) {
                $driver['clocks'] = $this->emptyClocks();
            }
        }
        unset($driver);
        return $drivers;
    }

    public function currentEvent(int $driverId): ?array
    {
        $stmt = $this->db->prepare("SELECT e.*, l.load_number, t.unit_number, tr.trailer_number, u.name AS created_by_name
            FROM hos_events e
            LEFT JOIN loads l ON l.id=e.load_id
            LEFT JOIN trucks t ON t.id=e.truck_id
            LEFT JOIN trailers tr ON tr.id=e.trailer_id
            LEFT JOIN users u ON u.id=e.created_by
            WHERE e.driver_id=? AND e.record_status='active'
            ORDER BY e.event_at DESC,e.id DESC LIMIT 1");
        $stmt->execute([$driverId]);
        return $stmt->fetch() ?: null;
    }

    public function storeEvent(int $driverId, array $data): int
    {
        $profile = $this->profileForDriver($driverId);
        $status = in_array(($data['duty_status'] ?? ''), self::DUTY_STATUSES, true) ? $data['duty_status'] : 'off_duty';
        $special = in_array(($data['special_status'] ?? ''), self::SPECIAL_STATUSES, true) ? $data['special_status'] : '';
        if ($special === 'personal_conveyance') {
            if (empty($profile['personal_conveyance_allowed'])) throw new RuntimeException('Personal conveyance is not enabled for this driver.');
            $status = 'off_duty';
        }
        if ($special === 'yard_move') {
            if (empty($profile['yard_move_allowed'])) throw new RuntimeException('Yard move is not enabled for this driver.');
            $status = 'on_duty';
        }
        $ruleSet = in_array(($data['rule_set'] ?? ''), self::RULE_SETS, true) ? $data['rule_set'] : (string) $profile['default_rule_set'];
        $jurisdiction = $ruleSet === 'us_property' ? 'us' : 'canada';
        $eventAt = $this->normalizeDateTime((string) ($data['event_at'] ?? ''), (string) $profile['home_terminal_timezone']);

        $current = $this->currentEvent($driverId);
        if ($current && $current['duty_status'] === $status && (string) ($current['special_status'] ?? '') === $special && strtotime((string) $current['event_at']) >= strtotime($eventAt) - 60) {
            throw new RuntimeException('This duty status is already active.');
        }

        $loadId = !empty($data['load_id']) ? (int) $data['load_id'] : null;
        $truckId = !empty($data['truck_id']) ? (int) $data['truck_id'] : null;
        $trailerId = !empty($data['trailer_id']) ? (int) $data['trailer_id'] : null;
        if (!$loadId || !$truckId) {
            $active = $this->activeAssignment($driverId);
            $loadId = $loadId ?: ($active['id'] ?? null);
            $truckId = $truckId ?: ($active['truck_id'] ?? null);
            $trailerId = $trailerId ?: ($active['trailer_id'] ?? null);
        }

        $logDate = $this->logDateForEvent($eventAt, $profile);
        $logId = $this->ensureDailyLog($driverId, $logDate, $ruleSet, $profile);
        $stmt = $this->db->prepare('INSERT INTO hos_events
            (driver_id, daily_log_id, event_at, duty_status, special_status, jurisdiction, rule_set, location_address, latitude, longitude, odometer, engine_hours, load_id, truck_id, trailer_id, annotation, origin, created_by)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
        $stmt->execute([
            $driverId,
            $logId,
            $eventAt,
            $status,
            $special ?: null,
            $jurisdiction,
            $ruleSet,
            trim((string) ($data['location_address'] ?? '')) ?: null,
            $this->nullableFloat($data['latitude'] ?? null),
            $this->nullableFloat($data['longitude'] ?? null),
            $this->nullableInt($data['odometer'] ?? null),
            $this->nullableFloat($data['engine_hours'] ?? null),
            $loadId,
            $truckId,
            $trailerId,
            trim((string) ($data['annotation'] ?? '')) ?: null,
            in_array(($data['origin'] ?? ''), ['manual','gps','eld_import','system'], true) ? $data['origin'] : 'manual',
            (int) (Auth::user()['id'] ?? 0) ?: null,
        ]);
        $id = (int) $this->db->lastInsertId();
        $this->markLogNeedsCertification($driverId, $logDate);
        $this->refreshDailyLog($driverId, $logDate);
        return $id;
    }

    public function eventsForDay(int $driverId, string $date): array
    {
        $profile = $this->profileForDriver($driverId);
        $tz = new DateTimeZone((string) $profile['home_terminal_timezone']);
        $start = (new DateTimeImmutable($date . ' ' . ($profile['log_day_start_time'] ?? '00:00:00'), $tz));
        $end = $start->modify('+1 day');
        $stmt = $this->db->prepare("SELECT e.*, l.load_number, t.unit_number, tr.trailer_number, u.name AS created_by_name
            FROM hos_events e
            LEFT JOIN loads l ON l.id=e.load_id
            LEFT JOIN trucks t ON t.id=e.truck_id
            LEFT JOIN trailers tr ON tr.id=e.trailer_id
            LEFT JOIN users u ON u.id=e.created_by
            WHERE e.driver_id=? AND e.record_status='active' AND e.event_at>=? AND e.event_at<?
            ORDER BY e.event_at,e.id");
        $stmt->execute([$driverId, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')]);
        return $stmt->fetchAll();
    }

    public function dailyLog(int $driverId, string $date): array
    {
        $profile = $this->profileForDriver($driverId);
        $stmt = $this->db->prepare('SELECT * FROM hos_daily_logs WHERE driver_id=? AND log_date=? LIMIT 1');
        $stmt->execute([$driverId, $date]);
        $log = $stmt->fetch();
        if (!$log) {
            $id = $this->ensureDailyLog($driverId, $date, (string) $profile['default_rule_set'], $profile);
            $stmt = $this->db->prepare('SELECT * FROM hos_daily_logs WHERE id=?');
            $stmt->execute([$id]);
            $log = $stmt->fetch();
        }
        return $log ?: [];
    }

    public function updateDailyLog(int $driverId, string $date, array $data): void
    {
        $profile = $this->profileForDriver($driverId);
        $ruleSet = in_array(($data['rule_set'] ?? ''), self::RULE_SETS, true) ? $data['rule_set'] : (string) $profile['default_rule_set'];
        $logId = $this->ensureDailyLog($driverId, $date, $ruleSet, $profile);
        $exceptions = [];
        foreach (['adverse_driving','short_haul','split_sleeper','off_duty_deferral_day1','off_duty_deferral_day2','ferry','oilfield_waiting','emergency'] as $key) {
            if (!empty($data[$key])) $exceptions[] = $key;
        }
        $stmt = $this->db->prepare('UPDATE hos_daily_logs SET rule_set=?, jurisdiction=?, cycle_code=?, vehicle_numbers=?, trailer_numbers=?, shipping_documents=?, start_odometer=?, end_odometer=?, exceptions=?, notes=? WHERE id=?');
        $cycle = $ruleSet === 'us_property' ? (string) $profile['us_cycle'] : (string) $profile['canada_cycle'];
        $stmt->execute([
            $ruleSet,
            $ruleSet === 'us_property' ? 'us' : 'canada',
            $cycle,
            trim((string) ($data['vehicle_numbers'] ?? '')) ?: null,
            trim((string) ($data['trailer_numbers'] ?? '')) ?: null,
            trim((string) ($data['shipping_documents'] ?? '')) ?: null,
            $this->nullableInt($data['start_odometer'] ?? null),
            $this->nullableInt($data['end_odometer'] ?? null),
            $exceptions ? json_encode($exceptions) : null,
            trim((string) ($data['notes'] ?? '')) ?: null,
            $logId,
        ]);
        $this->refreshDailyLog($driverId, $date);
    }

    public function dailySummary(int $driverId, string $date): array
    {
        $profile = $this->profileForDriver($driverId);
        $tz = new DateTimeZone((string) $profile['home_terminal_timezone']);
        $start = new DateTimeImmutable($date . ' ' . ($profile['log_day_start_time'] ?? '00:00:00'), $tz);
        $end = $start->modify('+1 day');
        $segments = $this->segmentsBetween($driverId, $start, $end);
        $totals = ['off_duty'=>0,'sleeper_berth'=>0,'driving'=>0,'on_duty'=>0];
        foreach ($segments as $segment) {
            $totals[$segment['duty_status']] += $segment['minutes'];
        }
        $log = $this->dailyLog($driverId, $date);
        $ruleSet = (string) ($log['rule_set'] ?? $profile['default_rule_set']);
        $violations = $this->dailyViolations($totals, $ruleSet, $segments);
        return [
            'date' => $date,
            'start_at' => $start->format('Y-m-d H:i:s'),
            'end_at' => $end->format('Y-m-d H:i:s'),
            'segments' => $segments,
            'totals' => $totals,
            'on_duty_total' => $totals['driving'] + $totals['on_duty'],
            'violations' => $violations,
            'log' => $log,
        ];
    }

    public function clockSummary(int $driverId, ?DateTimeImmutable $asOf = null): array
    {
        $profile = $this->profileForDriver($driverId);
        $tz = new DateTimeZone((string) $profile['home_terminal_timezone']);
        $asOf = $asOf ? $asOf->setTimezone($tz) : new DateTimeImmutable('now', $tz);
        $current = $this->currentEvent($driverId);
        $ruleSet = in_array(($current['rule_set'] ?? ''), self::RULE_SETS, true) ? $current['rule_set'] : (string) $profile['default_rule_set'];
        $isUs = $ruleSet === 'us_property';
        $restRequired = $isUs ? 600 : 480;
        $lookback = $asOf->modify('-16 days');
        $segments = $this->segmentsBetween($driverId, $lookback, $asOf);
        $lastRestEnd = $this->latestQualifyingRestEnd($segments, $restRequired, $lookback);
        $shiftSegments = array_values(array_filter($segments, static fn(array $s): bool => $s['end_ts'] > $lastRestEnd->getTimestamp()));
        $shiftDriving = 0;
        $shiftOnDuty = 0;
        foreach ($shiftSegments as $segment) {
            $minutes = $this->overlapMinutes($segment, $lastRestEnd, $asOf);
            if ($segment['duty_status'] === 'driving') $shiftDriving += $minutes;
            if (in_array($segment['duty_status'], ['driving','on_duty'], true)) $shiftOnDuty += $minutes;
        }
        $elapsed = max(0, (int) floor(($asOf->getTimestamp() - $lastRestEnd->getTimestamp()) / 60));

        if ($isUs) {
            $drivingLimit = 660;
            $dutyWindowLimit = 840;
            $cycleDays = $profile['us_cycle'] === '60_7' ? 7 : 8;
            $cycleLimit = $profile['us_cycle'] === '60_7' ? 3600 : 4200;
            $cycleStart = $asOf->modify('-' . $cycleDays . ' days');
            $cycleUsed = $this->onDutyMinutesBetween($driverId, $cycleStart, $asOf);
            $drivingSinceBreak = $this->drivingSinceBreak($shiftSegments, $lastRestEnd, $asOf);
            $breakRemaining = max(0, 480 - $drivingSinceBreak);
            $shiftRemaining = max(0, $dutyWindowLimit - $elapsed);
            $violations = [];
            if ($shiftDriving > $drivingLimit) $violations[] = '11-hour driving limit exceeded';
            if ($elapsed > $dutyWindowLimit && ($current['duty_status'] ?? '') === 'driving') $violations[] = '14-hour driving window exceeded';
            if ($drivingSinceBreak > 480 && ($current['duty_status'] ?? '') === 'driving') $violations[] = '30-minute break required';
            if ($cycleUsed > $cycleLimit) $violations[] = ($profile['us_cycle'] === '60_7' ? '60/7' : '70/8') . ' cycle limit exceeded';
            return [
                'rule_set'=>$ruleSet,'rule_label'=>'United States property-carrying','current_status'=>$current['duty_status'] ?? 'off_duty','special_status'=>$current['special_status'] ?? '',
                'drive_used'=>$shiftDriving,'drive_limit'=>$drivingLimit,'drive_remaining'=>max(0,$drivingLimit-$shiftDriving),
                'shift_used'=>$elapsed,'shift_limit'=>$dutyWindowLimit,'shift_remaining'=>$shiftRemaining,
                'on_duty_used'=>$shiftOnDuty,'on_duty_limit'=>$dutyWindowLimit,
                'break_used'=>$drivingSinceBreak,'break_limit'=>480,'break_remaining'=>$breakRemaining,
                'cycle_used'=>$cycleUsed,'cycle_limit'=>$cycleLimit,'cycle_remaining'=>max(0,$cycleLimit-$cycleUsed),
                'cycle_label'=>$profile['us_cycle']==='60_7'?'60 hours / 7 days':'70 hours / 8 days',
                'last_qualifying_rest_end'=>$lastRestEnd->format('Y-m-d H:i:s'),'violations'=>$violations,
                'daily_off_remaining'=>0,'elapsed_used'=>$elapsed,'elapsed_limit'=>$dutyWindowLimit,
            ];
        }

        $drivingLimit = $ruleSet === 'canada_north' ? 900 : 780;
        $onDutyLimit = $ruleSet === 'canada_north' ? 1080 : 840;
        $elapsedLimit = $ruleSet === 'canada_north' ? 1200 : 960;
        $cycleStart = $asOf->modify($profile['canada_cycle'] === 'cycle_2' ? '-14 days' : '-7 days');
        $cycleLimit = $profile['canada_cycle'] === 'cycle_2' ? 7200 : 4200;
        $cycleUsed = $this->onDutyMinutesBetween($driverId, $cycleStart, $asOf);
        $todayLogDate = $this->logDateForEvent($asOf->format('Y-m-d H:i:s'), $profile);
        $today = $this->dailySummary($driverId, $todayLogDate);
        $offToday = $today['totals']['off_duty'] + $today['totals']['sleeper_berth'];
        $violations = [];
        if ($shiftDriving > $drivingLimit) $violations[] = ($ruleSet === 'canada_north' ? '15-hour' : '13-hour') . ' driving limit exceeded';
        if ($shiftOnDuty > $onDutyLimit) $violations[] = ($ruleSet === 'canada_north' ? '18-hour' : '14-hour') . ' on-duty limit exceeded';
        if ($elapsed > $elapsedLimit && ($current['duty_status'] ?? '') === 'driving') $violations[] = ($ruleSet === 'canada_north' ? '20-hour' : '16-hour') . ' elapsed-time limit exceeded';
        if ($cycleUsed > $cycleLimit) $violations[] = ($profile['canada_cycle'] === 'cycle_2' ? 'Cycle 2 (120/14)' : 'Cycle 1 (70/7)') . ' limit exceeded';
        if ($offToday < 600 && $asOf->format('H:i') >= '23:59') $violations[] = '10 daily off-duty hours not completed';
        if ($profile['canada_cycle'] === 'cycle_2') {
            $last24 = $this->latestQualifyingRestEnd($segments, 1440, $lookback);
            $since24 = $this->onDutyMinutesBetween($driverId, $last24, $asOf);
            if ($since24 > 4200) $violations[] = 'Cycle 2 requires 24 consecutive hours off before exceeding 70 on-duty hours';
        }
        return [
            'rule_set'=>$ruleSet,'rule_label'=>$ruleSet==='canada_north'?'Canada north of 60°N':'Canada south of 60°N','current_status'=>$current['duty_status'] ?? 'off_duty','special_status'=>$current['special_status'] ?? '',
            'drive_used'=>$shiftDriving,'drive_limit'=>$drivingLimit,'drive_remaining'=>max(0,$drivingLimit-$shiftDriving),
            'shift_used'=>$shiftOnDuty,'shift_limit'=>$onDutyLimit,'shift_remaining'=>max(0,$onDutyLimit-$shiftOnDuty),
            'on_duty_used'=>$shiftOnDuty,'on_duty_limit'=>$onDutyLimit,
            'break_used'=>0,'break_limit'=>0,'break_remaining'=>0,
            'cycle_used'=>$cycleUsed,'cycle_limit'=>$cycleLimit,'cycle_remaining'=>max(0,$cycleLimit-$cycleUsed),
            'cycle_label'=>$profile['canada_cycle']==='cycle_2'?'Cycle 2 · 120 hours / 14 days':'Cycle 1 · 70 hours / 7 days',
            'last_qualifying_rest_end'=>$lastRestEnd->format('Y-m-d H:i:s'),'violations'=>$violations,
            'daily_off_remaining'=>max(0,600-$offToday),'elapsed_used'=>$elapsed,'elapsed_limit'=>$elapsedLimit,
        ];
    }

    public function logs(int $driverId, int $days = 14, ?string $endDate = null): array
    {
        $endDate = $endDate ?: date('Y-m-d');
        $startDate = (new DateTimeImmutable($endDate))->modify('-' . max(0, $days - 1) . ' days')->format('Y-m-d');
        $stmt = $this->db->prepare('SELECT * FROM hos_daily_logs WHERE driver_id=? AND log_date BETWEEN ? AND ? ORDER BY log_date DESC');
        $stmt->execute([$driverId, $startDate, $endDate]);
        $indexed = [];
        foreach ($stmt->fetchAll() as $row) $indexed[$row['log_date']] = $row;
        $results = [];
        $cursor = new DateTimeImmutable($endDate);
        for ($i=0; $i<$days; $i++) {
            $date = $cursor->modify('-' . $i . ' days')->format('Y-m-d');
            if (!isset($indexed[$date])) {
                $this->ensureDailyLog($driverId, $date, (string) $this->profileForDriver($driverId)['default_rule_set'], $this->profileForDriver($driverId));
            }
            $summary = $this->dailySummary($driverId, $date);
            $results[] = array_merge($summary['log'], ['summary'=>$summary]);
        }
        return $results;
    }

    public function certify(int $driverId, string $date, string $signature): void
    {
        $signature = trim($signature);
        if ($signature === '') throw new RuntimeException('Enter your legal name to certify the log.');
        $summary = $this->dailySummary($driverId, $date);
        $stmt = $this->db->prepare("UPDATE hos_daily_logs SET status='certified', certified_by=?, certification_name=?, certified_at=NOW(), violations=?, total_off_duty_minutes=?, total_sleeper_minutes=?, total_driving_minutes=?, total_on_duty_minutes=? WHERE driver_id=? AND log_date=?");
        $stmt->execute([
            (int) (Auth::user()['id'] ?? 0) ?: null,
            $signature,
            json_encode($summary['violations']),
            $summary['totals']['off_duty'],
            $summary['totals']['sleeper_berth'],
            $summary['totals']['driving'],
            $summary['totals']['on_duty'],
            $driverId,
            $date,
        ]);
    }

    public function createEditRequest(int $eventId, int $driverId, array $data): int
    {
        $event = $this->event($eventId);
        if (!$event || (int) $event['driver_id'] !== $driverId) throw new RuntimeException('Log event was not found.');
        $status = in_array(($data['proposed_duty_status'] ?? ''), self::DUTY_STATUSES, true) ? $data['proposed_duty_status'] : $event['duty_status'];
        $special = in_array(($data['proposed_special_status'] ?? ''), self::SPECIAL_STATUSES, true) ? $data['proposed_special_status'] : ($event['special_status'] ?? '');
        $profile = $this->profileForDriver($driverId);
        $eventAt = trim((string) ($data['proposed_event_at'] ?? '')) !== ''
            ? $this->normalizeDateTime((string) $data['proposed_event_at'], (string) $profile['home_terminal_timezone'])
            : (string) $event['event_at'];
        $reason = trim((string) ($data['reason'] ?? ''));
        if ($reason === '') throw new RuntimeException('An edit reason is required.');
        $stmt = $this->db->prepare('INSERT INTO hos_edit_requests (event_id, driver_id, proposed_event_at, proposed_duty_status, proposed_special_status, proposed_location_address, proposed_annotation, reason, requested_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
        $stmt->execute([
            $eventId,$driverId,$eventAt,$status,$special ?: null,
            trim((string) ($data['proposed_location_address'] ?? '')) ?: $event['location_address'],
            trim((string) ($data['proposed_annotation'] ?? '')) ?: null,
            $reason,(int) (Auth::user()['id'] ?? 0) ?: null,
        ]);
        return (int) $this->db->lastInsertId();
    }

    public function editRequests(?int $driverId = null, string $status = 'pending'): array
    {
        $sql = "SELECT r.*, d.name AS driver_name, e.event_at AS original_event_at, e.duty_status AS original_duty_status, e.special_status AS original_special_status, requester.name AS requester_name
            FROM hos_edit_requests r
            JOIN drivers d ON d.id=r.driver_id
            JOIN hos_events e ON e.id=r.event_id
            LEFT JOIN users requester ON requester.id=r.requested_by
            WHERE r.status=?";
        $params = [$status];
        if ($driverId) { $sql .= ' AND r.driver_id=?'; $params[] = $driverId; }
        $sql .= ' ORDER BY r.created_at DESC';
        $stmt = $this->db->prepare($sql); $stmt->execute($params); return $stmt->fetchAll();
    }

    public function reviewEdit(int $requestId, string $decision, ?string $reviewNotes = null): void
    {
        $decision = in_array($decision, ['approved','rejected'], true) ? $decision : 'rejected';
        $this->db->beginTransaction();
        try {
            $stmt = $this->db->prepare('SELECT r.*, e.* FROM hos_edit_requests r JOIN hos_events e ON e.id=r.event_id WHERE r.id=? FOR UPDATE');
            $stmt->execute([$requestId]);
            $row = $stmt->fetch();
            if (!$row || $row['status'] !== 'pending') throw new RuntimeException('Edit request is not available.');
            if ($decision === 'approved') {
                $insert = $this->db->prepare('INSERT INTO hos_events (driver_id,daily_log_id,event_at,duty_status,special_status,jurisdiction,rule_set,location_address,latitude,longitude,odometer,engine_hours,load_id,truck_id,trailer_id,annotation,origin,record_status,supersedes_event_id,created_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
                $insert->execute([
                    $row['driver_id'],$row['daily_log_id'],$row['proposed_event_at'],$row['proposed_duty_status'],$row['proposed_special_status'],$row['jurisdiction'],$row['rule_set'],$row['proposed_location_address'],$row['latitude'],$row['longitude'],$row['odometer'],$row['engine_hours'],$row['load_id'],$row['truck_id'],$row['trailer_id'],$row['proposed_annotation'] ?: $row['annotation'],'manual','active',$row['event_id'],(int) (Auth::user()['id'] ?? 0) ?: null,
                ]);
                $this->db->prepare("UPDATE hos_events SET record_status='superseded' WHERE id=?")->execute([$row['event_id']]);
                $editProfile = $this->profileForDriver((int) $row['driver_id']);
                $originalLogDate = $this->logDateForEvent((string) $row['event_at'], $editProfile);
                $proposedLogDate = $this->logDateForEvent((string) $row['proposed_event_at'], $editProfile);
                $this->markLogNeedsCertification((int) $row['driver_id'], $originalLogDate);
                if ($proposedLogDate !== $originalLogDate) $this->markLogNeedsCertification((int) $row['driver_id'], $proposedLogDate);
                $this->refreshDailyLog((int) $row['driver_id'], $originalLogDate);
                if ($proposedLogDate !== $originalLogDate) $this->refreshDailyLog((int) $row['driver_id'], $proposedLogDate);
            }
            $this->db->prepare('UPDATE hos_edit_requests SET status=?, reviewed_by=?, reviewed_at=NOW(), review_notes=? WHERE id=?')->execute([$decision,(int) (Auth::user()['id'] ?? 0) ?: null,trim((string)$reviewNotes)?:null,$requestId]);
            $this->db->commit();
        } catch (Throwable $exception) {
            $this->db->rollBack();
            throw $exception;
        }
    }

    public function event(int $id): ?array
    {
        $stmt = $this->db->prepare('SELECT * FROM hos_events WHERE id=?'); $stmt->execute([$id]); return $stmt->fetch() ?: null;
    }

    public function assignments(int $driverId): array
    {
        $stmt = $this->db->prepare("SELECT loads.id, loads.load_number, loads.truck_id, loads.trailer_id, trucks.unit_number, trailers.trailer_number
            FROM loads LEFT JOIN trucks ON trucks.id=loads.truck_id LEFT JOIN trailers ON trailers.id=loads.trailer_id
            WHERE loads.driver_id=? AND loads.status NOT IN ('paid','cancelled') ORDER BY loads.pickup_datetime DESC,loads.id DESC");
        $stmt->execute([$driverId]); return $stmt->fetchAll();
    }

    public function refreshDailyLog(int $driverId, string $date): void
    {
        $summary = $this->dailySummaryWithoutRefresh($driverId, $date);
        $stmt = $this->db->prepare('UPDATE hos_daily_logs SET total_off_duty_minutes=?, total_sleeper_minutes=?, total_driving_minutes=?, total_on_duty_minutes=?, violations=? WHERE driver_id=? AND log_date=?');
        $stmt->execute([$summary['totals']['off_duty'],$summary['totals']['sleeper_berth'],$summary['totals']['driving'],$summary['totals']['on_duty'],json_encode($summary['violations']),$driverId,$date]);
    }

    private function dailySummaryWithoutRefresh(int $driverId, string $date): array
    {
        $profile = $this->profileForDriver($driverId);
        $tz = new DateTimeZone((string) $profile['home_terminal_timezone']);
        $start = new DateTimeImmutable($date . ' ' . ($profile['log_day_start_time'] ?? '00:00:00'), $tz); $end = $start->modify('+1 day');
        $segments = $this->segmentsBetween($driverId,$start,$end);
        $totals = ['off_duty'=>0,'sleeper_berth'=>0,'driving'=>0,'on_duty'=>0];
        foreach($segments as $segment) $totals[$segment['duty_status']] += $segment['minutes'];
        $stmt=$this->db->prepare('SELECT rule_set FROM hos_daily_logs WHERE driver_id=? AND log_date=?');$stmt->execute([$driverId,$date]);
        $rule=(string)($stmt->fetchColumn()?:$profile['default_rule_set']);
        return ['start_at'=>$start->format('Y-m-d H:i:s'),'end_at'=>$end->format('Y-m-d H:i:s'),'segments'=>$segments,'totals'=>$totals,'violations'=>$this->dailyViolations($totals,$rule,$segments)];
    }

    private function ensureDailyLog(int $driverId, string $date, string $ruleSet, array $profile): int
    {
        $stmt = $this->db->prepare('SELECT id FROM hos_daily_logs WHERE driver_id=? AND log_date=?');
        $stmt->execute([$driverId,$date]); $id=(int)($stmt->fetchColumn()?:0); if($id) return $id;
        $cycle = $ruleSet === 'us_property' ? (string)$profile['us_cycle'] : (string)$profile['canada_cycle'];
        $stmt=$this->db->prepare('INSERT INTO hos_daily_logs (driver_id,log_date,jurisdiction,rule_set,cycle_code,home_terminal_timezone,status) VALUES (?,?,?,?,?,?,\'draft\')');
        $stmt->execute([$driverId,$date,$ruleSet==='us_property'?'us':'canada',$ruleSet,$cycle,(string)$profile['home_terminal_timezone']]);
        return (int)$this->db->lastInsertId();
    }

    private function markLogNeedsCertification(int $driverId,string $date): void
    {
        $this->db->prepare("UPDATE hos_daily_logs SET certified_at=CASE WHEN status='certified' THEN NULL ELSE certified_at END, status=CASE WHEN status='certified' THEN 'needs_recertification' ELSE status END WHERE driver_id=? AND log_date=?")->execute([$driverId,$date]);
    }

    private function activeAssignment(int $driverId): array
    {
        $stmt=$this->db->prepare("SELECT id,truck_id,trailer_id FROM loads WHERE driver_id=? AND status IN ('assigned','arrived_pickup','picked_up','in_transit','arrived_delivery') ORDER BY id DESC LIMIT 1");
        $stmt->execute([$driverId]); return $stmt->fetch()?:[];
    }

    private function segmentsBetween(int $driverId, DateTimeImmutable $start, DateTimeImmutable $end): array
    {
        $startString=$start->format('Y-m-d H:i:s');$endString=$end->format('Y-m-d H:i:s');
        $prior=$this->db->prepare("SELECT duty_status,special_status,event_at,rule_set FROM hos_events WHERE driver_id=? AND record_status='active' AND event_at<=? ORDER BY event_at DESC,id DESC LIMIT 1");
        $prior->execute([$driverId,$startString]);$priorRow=$prior->fetch();
        $stmt=$this->db->prepare("SELECT duty_status,special_status,event_at,rule_set FROM hos_events WHERE driver_id=? AND record_status='active' AND event_at>? AND event_at<? ORDER BY event_at,id");
        $stmt->execute([$driverId,$startString,$endString]);$events=$stmt->fetchAll();
        $status=$priorRow['duty_status']??'off_duty';$special=$priorRow['special_status']??null;$rule=$priorRow['rule_set']??null;$cursor=$start;
        $segments=[];
        foreach($events as $event){
            $eventTime=new DateTimeImmutable($event['event_at'],$start->getTimezone());
            if($eventTime>$cursor){$segments[]=$this->makeSegment($cursor,$eventTime,$status,$special,$rule);}
            $cursor=$eventTime;$status=$event['duty_status'];$special=$event['special_status'];$rule=$event['rule_set'];
        }
        if($cursor<$end)$segments[]=$this->makeSegment($cursor,$end,$status,$special,$rule);
        return $segments;
    }

    private function makeSegment(DateTimeImmutable $start,DateTimeImmutable $end,string $status,?string $special,?string $rule): array
    {
        return ['start'=>$start->format('Y-m-d H:i:s'),'end'=>$end->format('Y-m-d H:i:s'),'start_ts'=>$start->getTimestamp(),'end_ts'=>$end->getTimestamp(),'minutes'=>max(0,(int)floor(($end->getTimestamp()-$start->getTimestamp())/60)),'duty_status'=>$status,'special_status'=>$special,'rule_set'=>$rule];
    }

    private function latestQualifyingRestEnd(array $segments,int $requiredMinutes,DateTimeImmutable $fallback): DateTimeImmutable
    {
        $runStart=null;$runMinutes=0;$latest=null;$tz=$fallback->getTimezone();
        foreach($segments as $segment){
            if(in_array($segment['duty_status'],['off_duty','sleeper_berth'],true)){
                if($runStart===null)$runStart=$segment['start_ts'];
                $runMinutes += $segment['minutes'];
                if($runMinutes >= $requiredMinutes)$latest = $segment['end_ts'];
            }else{$runStart=null;$runMinutes=0;}
        }
        return $latest ? (new DateTimeImmutable('@'.$latest))->setTimezone($tz) : $fallback;
    }

    private function onDutyMinutesBetween(int $driverId,DateTimeImmutable $start,DateTimeImmutable $end): int
    {
        $minutes=0;foreach($this->segmentsBetween($driverId,$start,$end) as $segment)if(in_array($segment['duty_status'],['driving','on_duty'],true))$minutes+=$segment['minutes'];return $minutes;
    }

    private function drivingSinceBreak(array $segments,DateTimeImmutable $start,DateTimeImmutable $end): int
    {
        $driving=0;
        foreach($segments as $segment){
            $minutes=$this->overlapMinutes($segment,$start,$end);if($minutes<=0)continue;
            if($segment['duty_status']==='driving')$driving+=$minutes;elseif($minutes>=30)$driving=0;
        }
        return $driving;
    }

    private function overlapMinutes(array $segment,DateTimeImmutable $start,DateTimeImmutable $end): int
    {
        $from=max($segment['start_ts'],$start->getTimestamp());$to=min($segment['end_ts'],$end->getTimestamp());return max(0,(int)floor(($to-$from)/60));
    }

    private function dailyViolations(array $totals,string $ruleSet,array $segments): array
    {
        $violations=[];
        $onDuty=$totals['driving']+$totals['on_duty'];
        if($ruleSet==='us_property'){
            if($totals['driving']>660)$violations[]='More than 11 driving hours recorded';
            if($onDuty>840)$violations[]='More than 14 on-duty hours recorded';
        }elseif($ruleSet==='canada_north'){
            if($totals['driving']>900)$violations[]='More than 15 driving hours recorded';
            if($onDuty>1080)$violations[]='More than 18 on-duty hours recorded';
            if(($totals['off_duty']+$totals['sleeper_berth'])<480)$violations[]='Less than 8 off-duty hours recorded';
        }else{
            if($totals['driving']>780)$violations[]='More than 13 driving hours recorded';
            if($onDuty>840)$violations[]='More than 14 on-duty hours recorded';
            if(($totals['off_duty']+$totals['sleeper_berth'])<600)$violations[]='Less than 10 daily off-duty hours recorded';
        }
        return $violations;
    }

    private function logDateForEvent(string $eventAt, array $profile): string
    {
        $tz = new DateTimeZone((string) ($profile['home_terminal_timezone'] ?? 'America/Chicago'));
        $event = new DateTimeImmutable($eventAt, $tz);
        $startTime = (string) ($profile['log_day_start_time'] ?? '00:00:00');
        $dayStart = new DateTimeImmutable($event->format('Y-m-d') . ' ' . $startTime, $tz);
        return ($event < $dayStart ? $event->modify('-1 day') : $event)->format('Y-m-d');
    }

    private function normalizeDateTime(string $value,string $timezone): string
    {
        $tz=new DateTimeZone($timezone);
        if(trim($value)==='')return (new DateTimeImmutable('now',$tz))->format('Y-m-d H:i:s');
        return (new DateTimeImmutable($value,$tz))->format('Y-m-d H:i:s');
    }

    private function nullableFloat(mixed $value): ?float { return $value===''||$value===null?null:(float)$value; }
    private function nullableInt(mixed $value): ?int { return $value===''||$value===null?null:(int)$value; }

    private function emptyClocks(): array
    {
        return ['rule_set'=>'us_property','rule_label'=>'Not configured','current_status'=>'off_duty','special_status'=>'','drive_used'=>0,'drive_limit'=>660,'drive_remaining'=>660,'shift_used'=>0,'shift_limit'=>840,'shift_remaining'=>840,'on_duty_used'=>0,'on_duty_limit'=>840,'break_used'=>0,'break_limit'=>480,'break_remaining'=>480,'cycle_used'=>0,'cycle_limit'=>4200,'cycle_remaining'=>4200,'cycle_label'=>'70 hours / 8 days','last_qualifying_rest_end'=>null,'violations'=>[],'daily_off_remaining'=>0,'elapsed_used'=>0,'elapsed_limit'=>840];
    }
}
