<?php

namespace App\Services\Promotions;

use App\Models\StudentPromotionRecord;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
use RuntimeException;

/**
 * State machine for promotion records (plan sections 3, 4, 7).
 *
 * All status transitions go through this service so the audit trail
 * stays in sync (plan section 18).
 */
class PromotionStatusService
{
    public function __construct(private PromotionAuditService $audit)
    {
    }

    /**
     * Map of allowed transitions from current → set of next statuses.
     * Edge cases (manual overrides) are handled by `forceStatus`.
     */
    private const ALLOWED_TRANSITIONS = [
        StudentPromotionRecord::STATUS_NOT_REVIEWED => [
            StudentPromotionRecord::STATUS_ELIGIBLE,
            StudentPromotionRecord::STATUS_AWAITING_PARENT,
            StudentPromotionRecord::STATUS_CONDITIONAL,
            StudentPromotionRecord::STATUS_REPEATED,
            StudentPromotionRecord::STATUS_ON_HOLD,
            StudentPromotionRecord::STATUS_GRADUATED,
            StudentPromotionRecord::STATUS_WITHDRAWN,
        ],
        StudentPromotionRecord::STATUS_ELIGIBLE => [
            StudentPromotionRecord::STATUS_AWAITING_PARENT,
            StudentPromotionRecord::STATUS_ENROLLMENT_STARTED,
            StudentPromotionRecord::STATUS_CONDITIONAL,
            StudentPromotionRecord::STATUS_ON_HOLD,
            StudentPromotionRecord::STATUS_NOT_ENROLLED,
            StudentPromotionRecord::STATUS_WITHDRAWN,
        ],
        StudentPromotionRecord::STATUS_AWAITING_PARENT => [
            StudentPromotionRecord::STATUS_ENROLLMENT_STARTED,
            StudentPromotionRecord::STATUS_PROMOTED_AND_ENROLLED,
            StudentPromotionRecord::STATUS_NOT_ENROLLED,
            StudentPromotionRecord::STATUS_ON_HOLD,
            StudentPromotionRecord::STATUS_WITHDRAWN,
        ],
        StudentPromotionRecord::STATUS_ENROLLMENT_STARTED => [
            StudentPromotionRecord::STATUS_PROMOTED_AND_ENROLLED,
            StudentPromotionRecord::STATUS_AWAITING_PARENT,
            StudentPromotionRecord::STATUS_NOT_ENROLLED,
            StudentPromotionRecord::STATUS_ON_HOLD,
            StudentPromotionRecord::STATUS_WITHDRAWN,
        ],
        StudentPromotionRecord::STATUS_CONDITIONAL => [
            StudentPromotionRecord::STATUS_ELIGIBLE,
            StudentPromotionRecord::STATUS_AWAITING_PARENT,
            StudentPromotionRecord::STATUS_REPEATED,
            StudentPromotionRecord::STATUS_ON_HOLD,
            StudentPromotionRecord::STATUS_NOT_REVIEWED,
        ],
        StudentPromotionRecord::STATUS_ON_HOLD => [
            StudentPromotionRecord::STATUS_NOT_REVIEWED,
            StudentPromotionRecord::STATUS_ELIGIBLE,
            StudentPromotionRecord::STATUS_AWAITING_PARENT,
            StudentPromotionRecord::STATUS_REPEATED,
            StudentPromotionRecord::STATUS_CONDITIONAL,
            StudentPromotionRecord::STATUS_WITHDRAWN,
            StudentPromotionRecord::STATUS_NOT_ENROLLED,
        ],
        // Terminal statuses are intentionally not in the map (no auto
        // transitions); admins can still override via `forceStatus`.
    ];

    public function canTransition(string $from, string $to): bool
    {
        if ($from === $to) {
            return true;
        }
        if (!in_array($to, StudentPromotionRecord::ALL_STATUSES, true)) {
            return false;
        }
        $allowed = self::ALLOWED_TRANSITIONS[$from] ?? [];
        return in_array($to, $allowed, true);
    }

    /**
     * Apply a status transition with audit logging. Returns the saved
     * record. Throws if the new status is invalid or the transition is
     * not allowed.
     */
    public function transition(
        StudentPromotionRecord $record,
        string $newStatus,
        ?int $userId = null,
        ?string $notes = null
    ): StudentPromotionRecord {
        if (!in_array($newStatus, StudentPromotionRecord::ALL_STATUSES, true)) {
            throw new InvalidArgumentException(sprintf('Unknown promotion status "%s".', $newStatus));
        }

        $oldStatus = (string) $record->promotion_status;
        if ($oldStatus === $newStatus) {
            return $record;
        }
        if (!$this->canTransition($oldStatus, $newStatus)) {
            throw new RuntimeException(sprintf(
                'Transition from "%s" to "%s" is not allowed.',
                $oldStatus,
                $newStatus
            ));
        }

        return DB::transaction(function () use ($record, $oldStatus, $newStatus, $userId, $notes) {
            $record->promotion_status = $newStatus;
            $record->updated_by = $userId;
            $record->save();

            $this->audit->logStatusChange($record, $oldStatus, $newStatus, $userId, $notes);

            return $record;
        });
    }

    /**
     * Force a status change ignoring the transition map (e.g. admin
     * override). The audit trail records this as a manual override.
     */
    public function forceStatus(
        StudentPromotionRecord $record,
        string $newStatus,
        ?int $userId = null,
        ?string $notes = null
    ): StudentPromotionRecord {
        if (!in_array($newStatus, StudentPromotionRecord::ALL_STATUSES, true)) {
            throw new InvalidArgumentException(sprintf('Unknown promotion status "%s".', $newStatus));
        }

        $oldStatus = (string) $record->promotion_status;
        if ($oldStatus === $newStatus) {
            return $record;
        }

        return DB::transaction(function () use ($record, $oldStatus, $newStatus, $userId, $notes) {
            $record->promotion_status = $newStatus;
            $record->updated_by = $userId;
            $record->save();

            $this->audit->logManualOverride(
                $record,
                'promotion_status',
                $oldStatus,
                $newStatus,
                $userId,
                $notes ?? 'Manual status override'
            );

            return $record;
        });
    }
}
