<?php

declare(strict_types=1);

namespace App\Framework\Quality\PHPStan\Rules\Naming;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

/**
 * Enforces Controller Naming Convention:
 * - View Controllers: Show{Feature} (only GET requests, ViewResult)
 * - Action Controllers: {Verb}{Feature} (POST/PUT/DELETE, Business Logic)
 * - NO "Controller" suffix
 *
 * @implements Rule<InClassNode>
 */
final class ControllerNamingRule implements Rule
{
    public function getNodeType(): string
    {
        return InClassNode::class;
    }

    /**
     * @param InClassNode $node
     */
    public function processNode(Node $node, Scope $scope): array
    {
        $classReflection = $node->getClassReflection();
        $className = $classReflection->getName();
        $shortName = $classReflection->getNativeReflection()->getShortName();

        // Skip if not in Application namespace (controllers are in Application)
        if (! str_starts_with($className, 'App\\Application\\')) {
            return [];
        }

        // Check if class has Route attributes (controller indicator)
        $hasRouteAttribute = false;
        foreach ($classReflection->getNativeReflection()->getMethods() as $method) {
            foreach ($method->getAttributes() as $attribute) {
                if (str_contains($attribute->getName(), 'Route')) {
                    $hasRouteAttribute = true;

                    break 2;
                }
            }
        }

        if (! $hasRouteAttribute) {
            return []; // Not a controller
        }

        $errors = [];

        // Rule 1: NO "Controller" suffix
        if (str_ends_with($shortName, 'Controller')) {
            $errors[] = RuleErrorBuilder::message(sprintf(
                'Controller "%s" should not have "Controller" suffix. Use Show{Feature} for views or {Verb}{Feature} for actions.',
                $shortName
            ))
            ->identifier('naming.controller.noSuffix')
            ->build();
        }

        // Rule 2: Show* pattern validation
        if (str_starts_with($shortName, 'Show')) {
            $violations = $this->validateShowController($classReflection);
            foreach ($violations as $violation) {
                $errors[] = RuleErrorBuilder::message($violation)
                    ->identifier('naming.controller.showPattern')
                    ->build();
            }
        }

        // Rule 3: Action controller pattern (Submit, Create, Update, Delete, etc.)
        $actionVerbs = ['Submit', 'Create', 'Update', 'Delete', 'Handle', 'Process', 'Execute'];
        $startsWithActionVerb = false;
        foreach ($actionVerbs as $verb) {
            if (str_starts_with($shortName, $verb)) {
                $startsWithActionVerb = true;

                break;
            }
        }

        if (! str_starts_with($shortName, 'Show') && ! $startsWithActionVerb) {
            $errors[] = RuleErrorBuilder::message(sprintf(
                'Controller "%s" must start with "Show" (for views) or an action verb (Submit, Create, Update, Delete, Handle, Process, Execute).',
                $shortName
            ))
            ->identifier('naming.controller.pattern')
            ->build();
        }

        return $errors;
    }

    /**
     * @return string[]
     */
    private function validateShowController(\PHPStan\Reflection\ClassReflection $classReflection): array
    {
        $violations = [];
        $shortName = $classReflection->getNativeReflection()->getShortName();

        foreach ($classReflection->getNativeReflection()->getMethods() as $method) {
            if (! $method->isPublic() || $method->isStatic()) {
                continue;
            }

            // Check for Route attribute
            $routeAttributes = array_filter(
                $method->getAttributes(),
                fn ($attr) => str_contains($attr->getName(), 'Route')
            );

            foreach ($routeAttributes as $routeAttr) {
                $args = $routeAttr->getArguments();

                // Check if method is not GET (Show* should only have GET routes)
                if (isset($args['method'])) {
                    $methodName = is_object($args['method'])
                        ? $args['method']->name
                        : $args['method'];

                    if ($methodName !== 'GET' && ! str_contains((string)$methodName, 'GET')) {
                        $violations[] = sprintf(
                            'Show controller "%s" has non-GET route in method "%s()". Show controllers should only handle GET requests. Use %s for POST/PUT/DELETE.',
                            $shortName,
                            $method->getName(),
                            $this->suggestActionName($shortName, $methodName)
                        );
                    }
                }

                // Check return type (Show* should return ViewResult)
                $returnType = $method->getReturnType();
                if ($returnType && ! str_contains($returnType->__toString(), 'ViewResult')) {
                    $violations[] = sprintf(
                        'Show controller "%s" method "%s()" should return ViewResult, not "%s".',
                        $shortName,
                        $method->getName(),
                        $returnType->__toString()
                    );
                }
            }
        }

        return $violations;
    }

    private function suggestActionName(string $showClassName, string $httpMethod): string
    {
        $feature = str_replace('Show', '', $showClassName);

        return match(strtoupper($httpMethod)) {
            'POST' => "Submit{$feature}",
            'PUT', 'PATCH' => "Update{$feature}",
            'DELETE' => "Delete{$feature}",
            default => "{Verb}{$feature}"
        };
    }
}
