<?php

declare(strict_types=1);

namespace App\Framework\GraphQL\Validation;

use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;
use App\Framework\GraphQL\Execution\ParsedField;
use App\Framework\GraphQL\Execution\ParsedQuery;
use App\Framework\GraphQL\Schema\Schema;

/**
 * Analyzes GraphQL query complexity
 *
 * Prevents DoS attacks by limiting query complexity
 *
 * Complexity scoring:
 * - Each field: 1 point
 * - List fields: multiply by list size factor (default: 10)
 * - Nested fields: depth penalty
 */
final readonly class ComplexityAnalyzer
{
    public function __construct(
        private Schema $schema,
        private int $maxComplexity = 1000,
        private int $maxDepth = 10,
        private int $listMultiplier = 10
    ) {
    }

    /**
     * Analyze query complexity
     *
     * @throws FrameworkException if complexity exceeds limits
     */
    public function analyze(ParsedQuery $query): ComplexityScore
    {
        $score = ComplexityScore::zero();

        foreach ($query->fields as $field) {
            $score = $this->analyzeField($field, $score, 1);
        }

        // Check thresholds
        if ($score->exceedsThreshold($this->maxComplexity)) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                "Query complexity {$score->totalComplexity} exceeds maximum allowed {$this->maxComplexity}"
            )->withData([
                'complexity' => $score->totalComplexity,
                'max_allowed' => $this->maxComplexity,
                'max_depth' => $score->maxDepth,
                'field_count' => $score->fieldCount,
            ]);
        }

        if ($score->exceedsDepth($this->maxDepth)) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                "Query depth {$score->maxDepth} exceeds maximum allowed {$this->maxDepth}"
            )->withData([
                'depth' => $score->maxDepth,
                'max_allowed' => $this->maxDepth,
            ]);
        }

        return $score;
    }

    /**
     * Analyze single field complexity
     */
    private function analyzeField(ParsedField $field, ComplexityScore $score, int $depth): ComplexityScore
    {
        // Base cost: 1 per field
        $fieldCost = 1;

        // Check if field returns a list (increases complexity)
        if ($this->isListField($field)) {
            $fieldCost *= $this->listMultiplier;
        }

        // Apply depth penalty (exponential growth)
        $depthPenalty = pow(2, $depth - 1);
        $fieldCost *= $depthPenalty;

        $score = $score->add($fieldCost, $depth);

        // Recursively analyze nested selections
        foreach ($field->selections as $selection) {
            $score = $this->analyzeField($selection, $score, $depth + 1);
        }

        return $score;
    }

    /**
     * Check if field returns a list
     */
    private function isListField(ParsedField $field): bool
    {
        // Check if field name suggests list (plural)
        if (str_ends_with($field->name, 's') || str_ends_with($field->name, 'List')) {
            return true;
        }

        // Could also check schema type definition here
        // For now, use naming convention

        return false;
    }
}
