<?php

declare(strict_types=1);

namespace App\Framework\Mcp\Tools\Categories\Testing;

use App\Framework\Core\PathProvider;
use App\Framework\Mcp\McpTool;

/**
 * Testing and Test Coverage Analysis Tools for MCP
 * Provides test analysis, coverage reporting, and test quality metrics
 */
final readonly class TestingTools
{
    public function __construct(
        private PathProvider $pathProvider
    ) {
    }

    #[McpTool(
        name: 'analyze_test_coverage',
        description: 'Analyze test coverage and identify untested code paths'
    )]
    public function analyzeTestCoverage(?string $path = null): array
    {
        $sourcePath = $path ?? $this->pathProvider->getSourcePath();
        $testPath = $this->pathProvider->getBasePath() . '/tests';

        // Get all PHP files in source
        $sourceFiles = $this->getPhpFiles($sourcePath);
        $testFiles = $this->getPhpFiles($testPath);

        $coverage = $this->calculateCoverage($sourceFiles, $testFiles);

        return [
            'total_source_files' => count($sourceFiles),
            'total_test_files' => count($testFiles),
            'coverage_percentage' => $coverage['percentage'],
            'tested_files' => $coverage['tested'],
            'untested_files' => $coverage['untested'],
            'recommendations' => $this->generateCoverageRecommendations($coverage),
        ];
    }

    #[McpTool(
        name: 'find_missing_tests',
        description: 'Find classes and methods that need test coverage'
    )]
    public function findMissingTests(?string $className = null): array
    {
        if ($className) {
            return $this->analyzeSingleClass($className);
        }

        return $this->analyzeAllClasses();
    }

    #[McpTool(
        name: 'analyze_test_quality',
        description: 'Analyze quality of existing tests (assertions, coverage, etc.)'
    )]
    public function analyzeTestQuality(?string $testPath = null): array
    {
        $testPath = $testPath ?? $this->pathProvider->getBasePath() . '/tests';
        $testFiles = $this->getPhpFiles($testPath);

        $quality = [];
        foreach ($testFiles as $file) {
            $content = file_get_contents($file);
            $quality[] = [
                'file' => $file,
                'test_methods' => $this->countTestMethods($content),
                'assertions' => $this->countAssertions($content),
                'test_framework' => $this->detectTestFramework($content),
                'quality_score' => $this->calculateTestQualityScore($content),
                'suggestions' => $this->generateTestSuggestions($content),
            ];
        }

        return [
            'total_test_files' => count($testFiles),
            'test_quality_details' => $quality,
            'average_quality_score' => $this->calculateAverageQuality($quality),
            'framework_distribution' => $this->getFrameworkDistribution($quality),
        ];
    }

    #[McpTool(
        name: 'suggest_test_scenarios',
        description: 'Suggest test scenarios for a given class or method'
    )]
    public function suggestTestScenarios(string $className, ?string $methodName = null): array
    {
        $classPath = $this->findClassFile($className);
        if (! $classPath) {
            return ['error' => "Class {$className} not found"];
        }

        $content = file_get_contents($classPath);
        $scenarios = [];

        if ($methodName) {
            $scenarios = $this->analyzeMethodForTestScenarios($content, $methodName);
        } else {
            $scenarios = $this->analyzeClassForTestScenarios($content, $className);
        }

        return [
            'class' => $className,
            'method' => $methodName,
            'suggested_scenarios' => $scenarios,
            'example_tests' => $this->generateExampleTests($className, $scenarios),
        ];
    }

    #[McpTool(
        name: 'validate_test_structure',
        description: 'Validate test directory structure and naming conventions'
    )]
    public function validateTestStructure(): array
    {
        $testPath = $this->pathProvider->getBasePath() . '/tests';
        $sourcePath = $this->pathProvider->getSourcePath();

        $issues = [];
        $recommendations = [];

        // Check if test directory mirrors source structure
        $this->validateDirectoryMirroring($sourcePath, $testPath, $issues);

        // Check naming conventions
        $this->validateNamingConventions($testPath, $issues);

        // Check for proper test organization
        $this->validateTestOrganization($testPath, $issues, $recommendations);

        return [
            'structure_valid' => empty($issues),
            'issues_found' => $issues,
            'recommendations' => $recommendations,
            'test_path' => $testPath,
            'source_path' => $sourcePath,
        ];
    }

    private function getPhpFiles(string $directory): array
    {
        if (! is_dir($directory)) {
            return [];
        }

        $files = [];
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($directory)
        );

        foreach ($iterator as $file) {
            if ($file->isFile() && $file->getExtension() === 'php') {
                $files[] = $file->getPathname();
            }
        }

        return $files;
    }

    private function calculateCoverage(array $sourceFiles, array $testFiles): array
    {
        $tested = [];
        $untested = [];

        foreach ($sourceFiles as $sourceFile) {
            $className = $this->extractClassNameFromFile($sourceFile);
            if (! $className) {
                continue;
            }

            $hasTest = $this->hasCorrespondingTest($className, $testFiles);

            if ($hasTest) {
                $tested[] = [
                    'file' => $sourceFile,
                    'class' => $className,
                ];
            } else {
                $untested[] = [
                    'file' => $sourceFile,
                    'class' => $className,
                    'priority' => $this->calculateTestPriority($sourceFile),
                ];
            }
        }

        $percentage = count($sourceFiles) > 0
            ? round((count($tested) / count($sourceFiles)) * 100, 2)
            : 0;

        return [
            'percentage' => $percentage,
            'tested' => $tested,
            'untested' => $untested,
        ];
    }

    private function extractClassNameFromFile(string $filePath): ?string
    {
        $content = file_get_contents($filePath);

        // Extract namespace
        preg_match('/namespace\s+([^;]+);/', $content, $namespaceMatches);
        $namespace = $namespaceMatches[1] ?? '';

        // Extract class name
        preg_match('/(?:final\s+)?(?:readonly\s+)?class\s+(\w+)/', $content, $classMatches);
        $className = $classMatches[1] ?? null;

        return $className ? $namespace . '\\' . $className : null;
    }

    private function hasCorrespondingTest(string $className, array $testFiles): bool
    {
        $expectedTestName = basename(str_replace('\\', '/', $className)) . 'Test';

        foreach ($testFiles as $testFile) {
            if (str_contains($testFile, $expectedTestName)) {
                return true;
            }
        }

        return false;
    }

    private function calculateTestPriority(string $filePath): string
    {
        $content = file_get_contents($filePath);

        // Higher priority for controllers, services, and value objects
        if (str_contains($filePath, 'Controller')) {
            return 'high';
        }
        if (str_contains($filePath, 'Service')) {
            return 'high';
        }
        if (str_contains($filePath, 'ValueObjects')) {
            return 'medium';
        }
        if (str_contains($filePath, 'Repository')) {
            return 'high';
        }
        if (str_contains($content, 'public function')) {
            return 'medium';
        }

        return 'low';
    }

    private function countTestMethods(string $content): int
    {
        // Count Pest tests (it() functions)
        $pestTests = preg_match_all('/it\s*\(\s*[\'"]/', $content);

        // Count PHPUnit tests (test* methods)
        $phpunitTests = preg_match_all('/function\s+test\w+\s*\(/', $content);

        return $pestTests + $phpunitTests;
    }

    private function countAssertions(string $content): int
    {
        // Pest assertions
        $pestAssertions = preg_match_all('/expect\s*\(/', $content);

        // PHPUnit assertions
        $phpunitAssertions = preg_match_all('/\$this->assert\w+\s*\(/', $content);

        return $pestAssertions + $phpunitAssertions;
    }

    private function detectTestFramework(string $content): string
    {
        if (str_contains($content, 'expect(')) {
            return 'Pest';
        }
        if (str_contains($content, '$this->assert')) {
            return 'PHPUnit';
        }
        if (str_contains($content, 'describe(')) {
            return 'Pest';
        }

        return 'Unknown';
    }

    private function calculateTestQualityScore(string $content): int
    {
        $score = 0;

        // Points for having tests
        $testCount = $this->countTestMethods($content);
        $score += min($testCount * 10, 50);

        // Points for assertions
        $assertionCount = $this->countAssertions($content);
        $score += min($assertionCount * 5, 30);

        // Points for good structure
        if (str_contains($content, 'describe(') || str_contains($content, 'setUp')) {
            $score += 10;
        }
        if (str_contains($content, '// Arrange')) {
            $score += 5;
        }
        if (str_contains($content, '// Act')) {
            $score += 5;
        }

        return min($score, 100);
    }

    private function generateTestSuggestions(string $content): array
    {
        $suggestions = [];

        if ($this->countTestMethods($content) === 0) {
            $suggestions[] = 'Add test methods to this file';
        }

        if ($this->countAssertions($content) === 0) {
            $suggestions[] = 'Add assertions to verify expected behavior';
        }

        if (! str_contains($content, '// Arrange')) {
            $suggestions[] = 'Use Arrange-Act-Assert pattern for better test structure';
        }

        return $suggestions;
    }

    private function analyzeMethodForTestScenarios(string $content, string $methodName): array
    {
        $scenarios = [];

        // Basic scenarios for any method
        $scenarios[] = [
            'type' => 'happy_path',
            'description' => "Test {$methodName} with valid inputs",
            'example' => "it('executes {$methodName} successfully with valid data')",
        ];

        // Check for parameter validation
        if (str_contains($content, 'InvalidArgumentException')) {
            $scenarios[] = [
                'type' => 'validation',
                'description' => "Test {$methodName} with invalid parameters",
                'example' => "it('throws exception when {$methodName} receives invalid input')",
            ];
        }

        // Check for edge cases
        if (str_contains($content, 'empty') || str_contains($content, 'null')) {
            $scenarios[] = [
                'type' => 'edge_case',
                'description' => "Test {$methodName} with edge cases (null, empty)",
                'example' => "it('handles empty/null values correctly in {$methodName}')",
            ];
        }

        return $scenarios;
    }

    private function analyzeClassForTestScenarios(string $content, string $className): array
    {
        $scenarios = [];

        // Constructor tests
        if (str_contains($content, '__construct')) {
            $scenarios[] = [
                'type' => 'constructor',
                'description' => 'Test object creation and initialization',
                'example' => "it('can be instantiated with valid parameters')",
            ];
        }

        // Method tests
        preg_match_all('/public function (\w+)/', $content, $methods);
        foreach ($methods[1] as $method) {
            if ($method !== '__construct') {
                $scenarios[] = [
                    'type' => 'method',
                    'description' => "Test {$method} functionality",
                    'example' => "it('{$method} works correctly')",
                ];
            }
        }

        return $scenarios;
    }

    private function generateExampleTests(string $className, array $scenarios): array
    {
        $examples = [];

        foreach ($scenarios as $scenario) {
            $examples[] = [
                'scenario' => $scenario['description'],
                'pest_example' => $this->generatePestExample($className, $scenario),
                'phpunit_example' => $this->generatePhpUnitExample($className, $scenario),
            ];
        }

        return $examples;
    }

    private function generatePestExample(string $className, array $scenario): string
    {
        $shortName = basename(str_replace('\\', '/', $className));

        return match($scenario['type']) {
            'constructor' => "it('can create {$shortName}', function () {\n    \$instance = new {$shortName}();\n    expect(\$instance)->toBeInstanceOf({$shortName}::class);\n});",
            'method' => "it('{$scenario['description']}', function () {\n    // Arrange\n    \$instance = new {$shortName}();\n    \n    // Act & Assert\n    // TODO: Add test logic\n});",
            default => "it('{$scenario['description']}', function () {\n    // TODO: Implement test\n});"
        };
    }

    private function generatePhpUnitExample(string $className, array $scenario): string
    {
        $shortName = basename(str_replace('\\', '/', $className));
        $methodName = 'test' . str_replace(' ', '', ucwords($scenario['description']));

        return "public function {$methodName}(): void\n{\n    // Arrange\n    \$instance = new {$shortName}();\n    \n    // Act & Assert\n    \$this->assertInstanceOf({$shortName}::class, \$instance);\n}";
    }

    private function calculateAverageQuality(array $quality): float
    {
        if (empty($quality)) {
            return 0;
        }

        $total = array_sum(array_column($quality, 'quality_score'));

        return round($total / count($quality), 2);
    }

    private function getFrameworkDistribution(array $quality): array
    {
        $distribution = [];
        foreach ($quality as $item) {
            $framework = $item['test_framework'];
            $distribution[$framework] = ($distribution[$framework] ?? 0) + 1;
        }

        return $distribution;
    }

    private function generateCoverageRecommendations(array $coverage): array
    {
        $recommendations = [];

        if ($coverage['percentage'] < 50) {
            $recommendations[] = 'Test coverage is below 50%. Focus on testing critical business logic first.';
        }

        if ($coverage['percentage'] < 80) {
            $recommendations[] = 'Consider adding tests for Value Objects and Controllers to improve coverage.';
        }

        // Prioritize untested files
        $highPriority = array_filter($coverage['untested'], fn ($item) => $item['priority'] === 'high');
        if (! empty($highPriority)) {
            $recommendations[] = 'High priority classes need testing: ' .
                implode(', ', array_column($highPriority, 'class'));
        }

        return $recommendations;
    }

    private function findClassFile(string $className): ?string
    {
        $sourcePath = $this->pathProvider->getSourcePath();
        $relativePath = str_replace(['App\\', '\\'], ['', '/'], $className) . '.php';
        $fullPath = $sourcePath . '/' . $relativePath;

        return file_exists($fullPath) ? $fullPath : null;
    }

    private function validateDirectoryMirroring(string $sourcePath, string $testPath, array &$issues): void
    {
        // Check if major source directories have corresponding test directories
        $sourceDirectories = glob($sourcePath . '/*', GLOB_ONLYDIR);

        foreach ($sourceDirectories as $sourceDir) {
            $dirName = basename($sourceDir);
            $testDir = $testPath . '/' . $dirName;

            if (! is_dir($testDir)) {
                $issues[] = "Missing test directory for source directory: {$dirName}";
            }
        }
    }

    private function validateNamingConventions(string $testPath, array &$issues): void
    {
        $testFiles = $this->getPhpFiles($testPath);

        foreach ($testFiles as $testFile) {
            $filename = basename($testFile, '.php');

            if (! str_ends_with($filename, 'Test')) {
                $issues[] = "Test file doesn't follow naming convention: {$filename}";
            }
        }
    }

    private function validateTestOrganization(string $testPath, array &$issues, array &$recommendations): void
    {
        $testFiles = $this->getPhpFiles($testPath);

        if (empty($testFiles)) {
            $issues[] = 'No test files found in test directory';

            return;
        }

        // Check for pest.php configuration
        if (! file_exists($testPath . '/pest.php')) {
            $recommendations[] = 'Consider adding pest.php configuration file for Pest tests';
        }

        // Check for proper directory structure
        $hasUnitTests = is_dir($testPath . '/Unit');
        $hasFeatureTests = is_dir($testPath . '/Feature');

        if (! $hasUnitTests && ! $hasFeatureTests) {
            $recommendations[] = 'Consider organizing tests into Unit/ and Feature/ directories';
        }
    }
}
