<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use ReflectionClass;

class UpdateLogicalRelationships extends Command
{
    protected $signature = 'db:update-relationships';

    protected $description = '모델에서 논리적 관계를 추출하여 문서 업데이트';

    public function handle()
    {
        $this->info('🔄 논리적 관계 문서 업데이트 시작...');

        $relationships = $this->extractModelRelationships();
        $this->updateLogicalDocument($relationships);

        $this->info('✅ 논리적 관계 문서 업데이트 완료!');
    }

    private function extractModelRelationships(): array
    {
        $relationships = [];
        $modelPath = app_path('Models');

        // 모든 모델 파일 스캔
        $modelFiles = File::allFiles($modelPath);

        foreach ($modelFiles as $file) {
            if ($file->getExtension() !== 'php') {
                continue;
            }

            $className = $this->getClassNameFromFile($file);
            if (! $className || ! class_exists($className)) {
                continue;
            }

            try {
                $reflection = new ReflectionClass($className);

                // 모델이 Eloquent Model인지 확인
                if (! $reflection->isSubclassOf(\Illuminate\Database\Eloquent\Model::class)) {
                    continue;
                }

                // Abstract 클래스 건너뛰기
                if ($reflection->isAbstract()) {
                    continue;
                }

                // 테이블 이름 직접 추출
                $tableName = $this->getTableNameFromModel($className, $reflection);
                if (! $tableName) {
                    continue;
                }

                $relationships[$tableName] = [
                    'model' => $className,
                    'relationships' => $this->getModelRelationshipsFromFile($file, $className),
                ];

            } catch (\Exception $e) {
                $this->warn("모델 분석 실패: {$className} - ".$e->getMessage());

                continue;
            }
        }

        return $relationships;
    }

    private function getTableNameFromModel(string $className, ReflectionClass $reflection): ?string
    {
        // 클래스명에서 테이블명 추정
        $modelName = class_basename($className);
        $tableName = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $modelName));

        // 복수형으로 변환 (간단한 규칙)
        if (! str_ends_with($tableName, 's')) {
            $tableName .= 's';
        }

        return $tableName;
    }

    private function getModelRelationshipsFromFile($file, string $className): array
    {
        $content = File::get($file->getRealPath());
        $relationships = [];

        // use 문 추출
        $useStatements = $this->extractUseStatements($content);

        // 현재 파일의 namespace 추출
        $currentNamespace = $this->extractNamespace($content);

        // 관계 메서드 패턴 검출
        $patterns = [
            'belongsTo' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->belongsTo\s*\(\s*([^,\)]+)/',
            'hasMany' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->hasMany\s*\(\s*([^,\)]+)/',
            'hasOne' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->hasOne\s*\(\s*([^,\)]+)/',
            'belongsToMany' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->belongsToMany\s*\(\s*([^,\)]+)/',
            'morphTo' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->morphTo\s*\(/',
            'morphMany' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->morphMany\s*\(\s*([^,\)]+)/',
            'morphOne' => '/public\s+function\s+(\w+)\s*\([^)]*\)\s*[^{]*{\s*return\s+\$this->morphOne\s*\(\s*([^,\)]+)/',
        ];

        foreach ($patterns as $type => $pattern) {
            if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
                foreach ($matches as $match) {
                    // morphTo는 관련 모델이 없으므로 특별 처리
                    if ($type === 'morphTo') {
                        $relationships[] = [
                            'method' => $match[1],
                            'type' => $type,
                            'related_model' => '(Polymorphic)',
                            'foreign_key' => null,
                            'local_key' => null,
                        ];

                        continue;
                    }

                    // ::class 제거 및 따옴표 제거
                    $relatedModel = str_replace('::class', '', trim($match[2], '"\''));

                    // 완전한 클래스명으로 변환
                    $fullyQualifiedClass = $this->resolveClassName($relatedModel, $useStatements, $currentNamespace, $className);

                    $relationships[] = [
                        'method' => $match[1],
                        'type' => $type,
                        'related_model' => $fullyQualifiedClass,
                        'foreign_key' => null,
                        'local_key' => null,
                    ];
                }
            }
        }

        return $relationships;
    }

    /**
     * 파일 내용에서 use 문들을 추출
     */
    private function extractUseStatements(string $content): array
    {
        $useStatements = [];

        // use 문 패턴: use Full\Namespace\ClassName; 또는 use Full\Namespace\ClassName as Alias;
        if (preg_match_all('/use\s+([^;]+);/', $content, $matches)) {
            foreach ($matches[1] as $useStatement) {
                // as 별칭 처리
                if (strpos($useStatement, ' as ') !== false) {
                    [$fullClass, $alias] = array_map('trim', explode(' as ', $useStatement));
                    $useStatements[$alias] = $fullClass;
                } else {
                    $fullClass = trim($useStatement);
                    $shortName = class_basename($fullClass);
                    $useStatements[$shortName] = $fullClass;
                }
            }
        }

        return $useStatements;
    }

    /**
     * 파일 내용에서 namespace 추출
     */
    private function extractNamespace(string $content): ?string
    {
        if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) {
            return trim($matches[1]);
        }

        return null;
    }

    /**
     * 짧은 클래스명을 완전한 클래스명으로 변환
     */
    private function resolveClassName(string $className, array $useStatements, ?string $currentNamespace, string $currentClassName): string
    {
        // 이미 완전한 클래스명인 경우 (백슬래시 포함)
        if (strpos($className, '\\') !== false) {
            return ltrim($className, '\\');
        }

        // self/static 처리 - 현재 클래스로 대체
        if ($className === 'self' || $className === 'static') {
            return $currentClassName;
        }

        // use 문에서 찾기
        if (isset($useStatements[$className])) {
            return $useStatements[$className];
        }

        // 같은 namespace에 있다고 가정
        if ($currentNamespace) {
            return $currentNamespace.'\\'.$className;
        }

        // 그 외의 경우 그대로 반환
        return $className;
    }

    private function getModelRelationships(ReflectionClass $reflection, $model): array
    {
        $relationships = [];
        $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);

        foreach ($methods as $method) {
            if ($method->getDeclaringClass()->getName() !== $reflection->getName()) {
                continue;
            }

            try {
                // 관계 메서드 호출해서 타입 확인
                $result = $method->invoke($model);

                if ($this->isRelationshipMethod($result)) {
                    $relationships[] = [
                        'method' => $method->getName(),
                        'type' => $this->getRelationshipType($result),
                        'related_model' => get_class($result->getRelated()),
                        'foreign_key' => $this->getForeignKey($result),
                        'local_key' => $this->getLocalKey($result),
                    ];
                }
            } catch (\Exception $e) {
                // 관계 메서드가 아니거나 호출 실패 시 건너뛰기
                continue;
            }
        }

        return $relationships;
    }

    private function isRelationshipMethod($result): bool
    {
        return $result instanceof \Illuminate\Database\Eloquent\Relations\Relation;
    }

    private function getRelationshipType($relation): string
    {
        $className = get_class($relation);

        return class_basename($className);
    }

    private function getForeignKey($relation): ?string
    {
        return method_exists($relation, 'getForeignKeyName')
            ? $relation->getForeignKeyName()
            : null;
    }

    private function getLocalKey($relation): ?string
    {
        return method_exists($relation, 'getLocalKeyName')
            ? $relation->getLocalKeyName()
            : null;
    }

    private function getClassNameFromFile($file): ?string
    {
        $content = File::get($file->getRealPath());

        if (! preg_match('/namespace\s+([^;]+);/', $content, $namespaceMatches)) {
            return null;
        }

        if (! preg_match('/class\s+(\w+)/', $content, $classMatches)) {
            return null;
        }

        return $namespaceMatches[1].'\\'.$classMatches[1];
    }

    private function updateLogicalDocument(array $relationships): void
    {
        $documentPath = base_path('LOGICAL_RELATIONSHIPS.md');
        $timestamp = now()->format('Y-m-d H:i:s');

        $content = "# 논리적 데이터베이스 관계 문서\n\n";
        $content .= "> **자동 생성**: {$timestamp}\n";
        $content .= "> **소스**: Eloquent 모델 관계 분석\n\n";

        $content .= "## 📊 모델별 관계 현황\n\n";

        foreach ($relationships as $tableName => $info) {
            if (empty($info['relationships'])) {
                continue;
            }

            $content .= "### {$tableName}\n";
            $content .= "**모델**: `{$info['model']}`\n\n";

            foreach ($info['relationships'] as $rel) {
                try {
                    // Polymorphic 관계는 특별 표시
                    if ($rel['related_model'] === '(Polymorphic)') {
                        $content .= "- **{$rel['method']}()**: {$rel['type']} → `(Polymorphic)`\n";

                        continue;
                    }

                    // 관련 모델 클래스가 존재하는지 확인
                    if (! class_exists($rel['related_model'])) {
                        $this->warn("모델 클래스가 존재하지 않음: {$rel['related_model']}");

                        continue;
                    }

                    $relatedTable = (new $rel['related_model'])->getTable();
                    $content .= "- **{$rel['method']}()**: {$rel['type']} → `{$relatedTable}`";

                    if ($rel['foreign_key']) {
                        $content .= " (FK: `{$rel['foreign_key']}`)";
                    }

                    $content .= "\n";
                } catch (\Exception $e) {
                    $this->warn("관계 처리 실패: {$rel['method']} - ".$e->getMessage());

                    continue;
                }
            }

            $content .= "\n";
        }

        File::put($documentPath, $content);
        $this->info("📄 문서 업데이트: {$documentPath}");
    }
}
