#!/usr/bin/env python3
"""
스키마 분석기 - 코드와 DB 스키마를 비교하여 누락된 테이블/컬럼을 찾는 도구
"""

import os
import sys
import re
import json
from typing import Dict, List, Set, Tuple
from datetime import datetime
import psycopg2
from psycopg2.extras import RealDictCursor

# 프로젝트 루트를 Python 경로에 추가
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

class SchemaAnalyzer:
    def __init__(self):
        self.db_config = {
            'host': 'localhost',
            'port': 5432,
            'database': 'tk_mp_bom',
            'user': 'tkmp_user',
            'password': 'tkmp_password'
        }
        self.code_tables = {}
        self.db_tables = {}
        self.analysis_result = {
            'timestamp': datetime.now().isoformat(),
            'missing_tables': [],
            'missing_columns': [],
            'extra_tables': [],
            'schema_issues': []
        }

    def analyze_code_models(self) -> Dict[str, Dict]:
        """코드에서 SQLAlchemy 모델을 분석하여 테이블 구조 추출"""
        print("📋 코드 모델 분석 중...")
        
        model_files = [
            'backend/app/models.py',
            'backend/app/auth/models.py'
        ]
        
        for file_path in model_files:
            if os.path.exists(file_path):
                self._parse_model_file(file_path)
        
        print(f"✅ 코드에서 {len(self.code_tables)}개 테이블 발견")
        return self.code_tables

    def _parse_model_file(self, file_path: str):
        """모델 파일을 파싱하여 테이블 정보 추출"""
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
        
        # 클래스 정의 찾기
        class_pattern = r'class\s+(\w+)\(Base\):(.*?)(?=class\s+\w+\(Base\):|$)'
        classes = re.findall(class_pattern, content, re.DOTALL)
        
        for class_name, class_content in classes:
            table_info = self._parse_class_content(class_name, class_content)
            if table_info:
                table_name = table_info['table_name']
                self.code_tables[table_name] = table_info

    def _parse_class_content(self, class_name: str, content: str) -> Dict:
        """클래스 내용을 파싱하여 테이블 정보 추출"""
        # __tablename__ 찾기
        tablename_match = re.search(r'__tablename__\s*=\s*["\']([^"\']+)["\']', content)
        if not tablename_match:
            return None
        
        table_name = tablename_match.group(1)
        
        # 컬럼 정의 찾기
        column_pattern = r'(\w+)\s*=\s*Column\((.*?)\)'
        columns = {}
        
        for match in re.finditer(column_pattern, content, re.DOTALL):
            column_name = match.group(1)
            column_def = match.group(2)
            
            # 컬럼 타입과 속성 파싱
            column_info = self._parse_column_definition(column_def)
            columns[column_name] = column_info
        
        return {
            'class_name': class_name,
            'table_name': table_name,
            'columns': columns,
            'file_path': None  # 나중에 설정
        }

    def _parse_column_definition(self, column_def: str) -> Dict:
        """컬럼 정의를 파싱하여 타입과 속성 추출"""
        # 기본 타입 매핑
        type_mapping = {
            'Integer': 'integer',
            'String': 'character varying',
            'Text': 'text',
            'Boolean': 'boolean',
            'DateTime': 'timestamp without time zone',
            'Numeric': 'numeric',
            'JSON': 'json'
        }
        
        # 타입 추출
        type_match = re.search(r'(Integer|String|Text|Boolean|DateTime|Numeric|JSON)', column_def)
        column_type = 'unknown'
        if type_match:
            sqlalchemy_type = type_match.group(1)
            column_type = type_mapping.get(sqlalchemy_type, sqlalchemy_type.lower())
        
        # 속성 추출
        nullable = 'nullable=False' not in column_def
        primary_key = 'primary_key=True' in column_def
        unique = 'unique=True' in column_def
        default = None
        
        # default 값 추출
        default_match = re.search(r'default=([^,)]+)', column_def)
        if default_match:
            default = default_match.group(1).strip()
        
        return {
            'type': column_type,
            'nullable': nullable,
            'primary_key': primary_key,
            'unique': unique,
            'default': default
        }

    def analyze_db_schema(self) -> Dict[str, Dict]:
        """실제 DB 스키마 분석"""
        print("🗄️  DB 스키마 분석 중...")
        
        try:
            conn = psycopg2.connect(**self.db_config)
            cursor = conn.cursor(cursor_factory=RealDictCursor)
            
            # 테이블 목록 조회
            cursor.execute("""
                SELECT tablename 
                FROM pg_tables 
                WHERE schemaname = 'public' 
                ORDER BY tablename
            """)
            
            tables = [row['tablename'] for row in cursor.fetchall()]
            
            # 각 테이블의 컬럼 정보 조회
            for table_name in tables:
                cursor.execute("""
                    SELECT 
                        column_name,
                        data_type,
                        is_nullable,
                        column_default,
                        character_maximum_length
                    FROM information_schema.columns 
                    WHERE table_name = %s 
                    AND table_schema = 'public'
                    ORDER BY ordinal_position
                """, (table_name,))
                
                columns = {}
                for row in cursor.fetchall():
                    columns[row['column_name']] = {
                        'type': row['data_type'],
                        'nullable': row['is_nullable'] == 'YES',
                        'default': row['column_default'],
                        'max_length': row['character_maximum_length']
                    }
                
                self.db_tables[table_name] = {
                    'table_name': table_name,
                    'columns': columns
                }
            
            cursor.close()
            conn.close()
            
            print(f"✅ DB에서 {len(self.db_tables)}개 테이블 발견")
            return self.db_tables
            
        except Exception as e:
            print(f"❌ DB 연결 실패: {e}")
            return {}

    def compare_schemas(self) -> Dict:
        """코드와 DB 스키마 비교"""
        print("🔍 스키마 비교 중...")
        
        code_table_names = set(self.code_tables.keys())
        db_table_names = set(self.db_tables.keys())
        
        # 누락된 테이블 (코드에는 있지만 DB에는 없음)
        missing_tables = code_table_names - db_table_names
        self.analysis_result['missing_tables'] = list(missing_tables)
        
        # 추가 테이블 (DB에는 있지만 코드에는 없음)
        extra_tables = db_table_names - code_table_names
        self.analysis_result['extra_tables'] = list(extra_tables)
        
        # 공통 테이블의 컬럼 비교
        common_tables = code_table_names & db_table_names
        
        for table_name in common_tables:
            missing_columns = self._compare_table_columns(table_name)
            if missing_columns:
                self.analysis_result['missing_columns'].extend(missing_columns)
        
        return self.analysis_result

    def _compare_table_columns(self, table_name: str) -> List[Dict]:
        """특정 테이블의 컬럼 비교"""
        code_columns = set(self.code_tables[table_name]['columns'].keys())
        db_columns = set(self.db_tables[table_name]['columns'].keys())
        
        missing_columns = []
        
        # 누락된 컬럼들
        for column_name in code_columns - db_columns:
            column_info = self.code_tables[table_name]['columns'][column_name]
            missing_columns.append({
                'table': table_name,
                'column': column_name,
                'type': column_info['type'],
                'nullable': column_info['nullable'],
                'default': column_info['default']
            })
        
        return missing_columns

    def generate_migration_sql(self) -> str:
        """누락된 스키마에 대한 마이그레이션 SQL 생성"""
        sql_statements = []
        sql_statements.append("-- 자동 생성된 마이그레이션 SQL")
        sql_statements.append(f"-- 생성 시간: {datetime.now()}")
        sql_statements.append("")
        
        # 누락된 테이블 생성
        for table_name in self.analysis_result['missing_tables']:
            if table_name in self.code_tables:
                create_sql = self._generate_create_table_sql(table_name)
                sql_statements.append(create_sql)
                sql_statements.append("")
        
        # 누락된 컬럼 추가
        for missing_col in self.analysis_result['missing_columns']:
            alter_sql = self._generate_add_column_sql(missing_col)
            sql_statements.append(alter_sql)
        
        return "\n".join(sql_statements)

    def _generate_create_table_sql(self, table_name: str) -> str:
        """테이블 생성 SQL 생성"""
        table_info = self.code_tables[table_name]
        columns = []
        
        for col_name, col_info in table_info['columns'].items():
            col_def = f"    {col_name} {col_info['type']}"
            
            if not col_info['nullable']:
                col_def += " NOT NULL"
            
            if col_info['default']:
                col_def += f" DEFAULT {col_info['default']}"
            
            if col_info['primary_key']:
                col_def += " PRIMARY KEY"
            
            columns.append(col_def)
        
        return f"CREATE TABLE {table_name} (\n" + ",\n".join(columns) + "\n);"

    def _generate_add_column_sql(self, missing_col: Dict) -> str:
        """컬럼 추가 SQL 생성"""
        sql = f"ALTER TABLE {missing_col['table']} ADD COLUMN {missing_col['column']} {missing_col['type']}"
        
        if missing_col['default']:
            sql += f" DEFAULT {missing_col['default']}"
        
        if not missing_col['nullable']:
            sql += " NOT NULL"
        
        return sql + ";"

    def save_analysis_report(self, filename: str = "schema_analysis_report.json"):
        """분석 결과를 JSON 파일로 저장"""
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(self.analysis_result, f, indent=2, ensure_ascii=False)
        
        print(f"📄 분석 보고서 저장: {filename}")

    def print_summary(self):
        """분석 결과 요약 출력"""
        print("\n" + "="*60)
        print("📊 스키마 분석 결과 요약")
        print("="*60)
        
        print(f"🔍 분석 시간: {self.analysis_result['timestamp']}")
        print(f"📋 코드 테이블: {len(self.code_tables)}개")
        print(f"🗄️  DB 테이블: {len(self.db_tables)}개")
        
        if self.analysis_result['missing_tables']:
            print(f"\n❌ 누락된 테이블 ({len(self.analysis_result['missing_tables'])}개):")
            for table in self.analysis_result['missing_tables']:
                print(f"   - {table}")
        
        if self.analysis_result['missing_columns']:
            print(f"\n❌ 누락된 컬럼 ({len(self.analysis_result['missing_columns'])}개):")
            for col in self.analysis_result['missing_columns']:
                print(f"   - {col['table']}.{col['column']} ({col['type']})")
        
        if self.analysis_result['extra_tables']:
            print(f"\n➕ 추가 테이블 ({len(self.analysis_result['extra_tables'])}개):")
            for table in self.analysis_result['extra_tables']:
                print(f"   - {table}")
        
        if not any([self.analysis_result['missing_tables'], 
                   self.analysis_result['missing_columns']]):
            print("\n✅ 스키마가 완전히 동기화되어 있습니다!")

def main():
    print("🚀 TK-MP-Project 스키마 분석기 시작")
    
    analyzer = SchemaAnalyzer()
    
    # 1. 코드 모델 분석
    analyzer.analyze_code_models()
    
    # 2. DB 스키마 분석
    analyzer.analyze_db_schema()
    
    # 3. 스키마 비교
    analyzer.compare_schemas()
    
    # 4. 결과 출력
    analyzer.print_summary()
    
    # 5. 마이그레이션 SQL 생성
    if analyzer.analysis_result['missing_tables'] or analyzer.analysis_result['missing_columns']:
        migration_sql = analyzer.generate_migration_sql()
        
        with open('migration.sql', 'w', encoding='utf-8') as f:
            f.write(migration_sql)
        
        print(f"\n📝 마이그레이션 SQL 생성: migration.sql")
        print("다음 명령으로 실행 가능:")
        print("docker exec tk-mp-postgres psql -U tkmp_user -d tk_mp_bom -f /path/to/migration.sql")
    
    # 6. 분석 보고서 저장
    analyzer.save_analysis_report()
    
    return analyzer.analysis_result

if __name__ == "__main__":
    main()
