<?php
declare(strict_types=1);

namespace App\Modules\Workflow\Services;

use App\Core\App;
use App\Core\Logger;
use App\Core\Registries\ActionRegistry;
use App\Modules\Workflow\Models\WorkflowInstance;

final class WorkflowGuardEvaluator
{
    /**
     * Evaluate a single guard condition against a workflow instance.
     */
    public static function evaluate(array $guard, WorkflowInstance $instance): bool
    {
        $type = $guard['type'] ?? '';
        $value = $guard['value'] ?? '';

        return match ($type) {
            'permission' => self::evaluatePermission($value),
            'role'       => self::evaluateRole($value),
            'condition'  => self::evaluateCondition($value, $instance),
            'timeout'    => self::evaluateTimeout($guard, $instance),
            default      => self::evaluateCustom($type, $value, $instance),
        };
    }

    /**
     * Check if current employee has the required permission.
     */
    private static function evaluatePermission(string $permissionKey): bool
    {
        $employee = App::getInstance()->currentEmployee();
        if (!$employee) {
            return false;
        }

        if (method_exists($employee, 'hasPermission')) {
            return $employee->hasPermission($permissionKey);
        }

        return false;
    }

    /**
     * Check if current employee has the required role.
     */
    private static function evaluateRole(string $roleCode): bool
    {
        $employee = App::getInstance()->currentEmployee();
        if (!$employee) {
            return false;
        }

        if (method_exists($employee, 'hasRole')) {
            return $employee->hasRole($roleCode);
        }

        return false;
    }

    /**
     * Evaluate a named condition.
     * Conditions can be registered by other modules via ActionRegistry.
     */
    private static function evaluateCondition(string $conditionName, WorkflowInstance $instance): bool
    {
        // Check if a condition handler is registered
        $handler = ActionRegistry::get('guard_condition.' . $conditionName);
        if ($handler !== null && isset($handler['handler']) && is_callable($handler['handler'])) {
            return (bool) ($handler['handler'])([
                'instance'    => $instance,
                'entity_type' => $instance->entity_type,
                'entity_id'   => (int) $instance->entity_id,
                'state_data'  => $instance->getStateData(),
            ]);
        }

        // Built-in conditions
        return match ($conditionName) {
            'has_payment_receipt'  => self::checkHasPaymentReceipt($instance),
            'is_subscription_paid' => self::checkSubscriptionPaid($instance),
            'within_appeal_window' => self::checkWithinAppealWindow($instance),
            'five_years_unpaid'    => self::checkFiveYearsUnpaid($instance),
            default => self::evaluateFallbackCondition($conditionName, $instance),
        };
    }

    /**
     * Evaluate timeout guard — has enough time passed since entering current state?
     */
    private static function evaluateTimeout(array $guard, WorkflowInstance $instance): bool
    {
        $days = (int) ($guard['days'] ?? 0);
        if ($days <= 0) {
            return false;
        }

        $enteredAt = strtotime($instance->state_entered_at);
        if ($enteredAt === false) {
            return false;
        }

        $timeoutAt = $enteredAt + ($days * 86400);
        return time() >= $timeoutAt;
    }

    /**
     * Custom guard type — check ActionRegistry for a handler.
     */
    private static function evaluateCustom(string $type, string $value, WorkflowInstance $instance): bool
    {
        $handler = ActionRegistry::get('guard_type.' . $type);
        if ($handler !== null && isset($handler['handler']) && is_callable($handler['handler'])) {
            return (bool) ($handler['handler'])([
                'value'       => $value,
                'instance'    => $instance,
                'entity_type' => $instance->entity_type,
                'entity_id'   => (int) $instance->entity_id,
            ]);
        }

        Logger::warning("Unknown guard type with no registered handler: {$type}", [
            'value'       => $value,
            'instance_id' => (int) $instance->id,
        ]);

        return true; // Unknown guards pass by default to not block workflows
    }

    // ── Built-in condition implementations ──

    private static function checkHasPaymentReceipt(WorkflowInstance $instance): bool
    {
        $db = App::getInstance()->db();

        // Check if payments table exists
        if (!$db->tableExists('payments')) {
            // Phase 11 not yet built — check state_data for manual receipt
            $stateData = $instance->getStateData();
            return !empty($stateData['receipt_number']);
        }

        $payment = $db->selectOne(
            "SELECT id FROM payments WHERE related_entity_type = ? AND related_entity_id = ? AND is_voided = 0 LIMIT 1",
            [$instance->entity_type, (int) $instance->entity_id]
        );

        return $payment !== null;
    }

    private static function checkSubscriptionPaid(WorkflowInstance $instance): bool
    {
        $db = App::getInstance()->db();

        if (!$db->tableExists('subscriptions')) {
            return true; // Phase 12 not yet built
        }

        $currentYear = date('Y');
        $unpaid = $db->selectOne(
            "SELECT id FROM subscriptions WHERE member_id = ? AND financial_year = ? AND status NOT IN ('paid', 'exempt') LIMIT 1",
            [(int) $instance->entity_id, $currentYear]
        );

        return $unpaid === null;
    }

    private static function checkWithinAppealWindow(WorkflowInstance $instance): bool
    {
        $enteredAt = strtotime($instance->state_entered_at);
        if ($enteredAt === false) {
            return false;
        }

        // Default 15 days appeal window — can be overridden by rules engine
        $appealDays = 15;
        try {
            $db = App::getInstance()->db();
            if ($db->tableExists('business_rules')) {
                $rule = $db->selectOne(
                    "SELECT current_value_json FROM business_rules WHERE rule_code = 'APPEAL_WINDOW' AND is_active = 1 AND branch_id IS NULL",
                    []
                );
                if ($rule) {
                    $val = json_decode($rule['current_value_json'], true);
                    $appealDays = (int) ($val['days'] ?? 15);
                }
            }
        } catch (\Throwable $e) {
            // Use default
        }

        $deadline = $enteredAt + ($appealDays * 86400);
        return time() <= $deadline;
    }

    private static function checkFiveYearsUnpaid(WorkflowInstance $instance): bool
    {
        $db = App::getInstance()->db();

        if (!$db->tableExists('subscriptions')) {
            return false; // Phase 12 not yet built
        }

        $currentYear = (int) date('Y');
        $unpaidYears = 0;

        for ($y = $currentYear; $y >= $currentYear - 6; $y--) {
            $unpaid = $db->selectOne(
                "SELECT id FROM subscriptions WHERE member_id = ? AND financial_year = ? AND status NOT IN ('paid', 'exempt') LIMIT 1",
                [(int) $instance->entity_id, (string) $y]
            );
            if ($unpaid) {
                $unpaidYears++;
            } else {
                break;
            }
        }

        return $unpaidYears >= 5;
    }

    private static function evaluateFallbackCondition(string $conditionName, WorkflowInstance $instance): bool
    {
        // Check state_data for manual condition flags
        $stateData = $instance->getStateData();
        if (isset($stateData[$conditionName])) {
            return (bool) $stateData[$conditionName];
        }

        Logger::warning("Unknown condition with no handler and no state_data flag: {$conditionName}", [
            'instance_id' => (int) $instance->id,
        ]);

        return false;
    }
}