#!/usr/bin/env python3
"""
QC Center Code Validator
품질 검증 시스템 - 워크플로우, 코드 품질, 보안 검증

사용법:
    python3 code-validator.py all <파일경로> [--json]
    python3 code-validator.py workflow <파일경로> [--json]
    python3 code-validator.py code <파일경로> [--json]
    python3 code-validator.py security <파일경로> [--json]
"""

import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional

# 로깅 설정
try:
    from logging_config import log_error, log_execution, setup_logging
except ImportError:
    sys.path.insert(0, str(Path(__file__).parent))
    from logging_config import log_error, log_execution, setup_logging


class QCValidator:
    """품질 검증 클래스"""

    def __init__(self, file_path: str, json_output: bool = False):
        self.file_path = Path(file_path)
        self.json_output = json_output
        self.logger = setup_logging("qc.code_validator")
        self.results = {"file": str(self.file_path), "all_passed": True, "details": {}}
        log_execution(self.logger, "QCValidator initialized", {"file": str(self.file_path)})

    def is_requirements_file(self) -> bool:
        """requirements.txt 파일인지 확인"""
        return "requirements.txt" in str(self.file_path)

    def validate_file_exists(self) -> bool:
        """파일 존재 확인"""
        if not self.file_path.exists():
            error_msg = f"File not found: {self.file_path}"
            self.results["error"] = error_msg
            self.results["all_passed"] = False
            log_error(self.logger, FileNotFoundError(error_msg), {"file": str(self.file_path)})
            return False
        return True

    def detect_language(self) -> str:
        """파일 확장자로 언어 감지"""
        ext = self.file_path.suffix.lower()
        lang_map = {
            ".py": "python",
            ".js": "javascript",
            ".ts": "javascript",
            ".jsx": "javascript",
            ".tsx": "javascript",
        }
        return lang_map.get(ext, "unknown")

    def validate_workflow(self) -> Dict[str, Any]:
        """
        워크플로우 검증 (Module B 체크리스트)
        WORKFLOW-QUALITY-CONTROL.md 기준
        """
        workflow_result = {"passed": True, "checks": {}}

        if not self.validate_file_exists():
            workflow_result["passed"] = False
            return workflow_result

        try:
            content = self.file_path.read_text(encoding="utf-8")
        except Exception as e:
            workflow_result["passed"] = False
            workflow_result["error"] = str(e)
            return workflow_result

        # 체크 1: Placeholder 확인
        placeholder_patterns = [
            r"TODO:",
            r"FIXME:",
            r"나중에\s*연동",
            r"나중에\s*구현",
            r"placeholder",
            r"PLACEHOLDER",
            r"NotImplementedError",
            r"pass\s*# TODO",
        ]

        placeholders_found = []
        for pattern in placeholder_patterns:
            matches = re.findall(pattern, content, re.IGNORECASE)
            if matches:
                placeholders_found.extend(matches)

        workflow_result["checks"]["no_placeholders"] = {
            "passed": len(placeholders_found) == 0,
            "found": len(placeholders_found),
            "items": placeholders_found[:5] if placeholders_found else [],
        }

        # 체크 2: 에러 처리 확인
        has_try_except = bool(re.search(r"try\s*:", content))
        has_error_handling = bool(re.search(r"(except|raise|Error)", content))

        workflow_result["checks"]["error_handling"] = {
            "passed": has_try_except or has_error_handling,
            "has_try_except": has_try_except,
            "has_error_handling": has_error_handling,
        }

        # 체크 3: 문서화 확인
        has_docstring = bool(re.search(r'""".*?"""', content, re.DOTALL))
        has_comments = bool(re.search(r"#\s*\w+", content))

        workflow_result["checks"]["documentation"] = {
            "passed": has_docstring or has_comments,
            "has_docstring": has_docstring,
            "has_comments": has_comments,
        }

        # 체크 4: 함수/클래스 정의 확인
        has_functions = bool(re.search(r"def\s+\w+", content))
        has_classes = bool(re.search(r"class\s+\w+", content))

        workflow_result["checks"]["code_structure"] = {
            "passed": has_functions or has_classes,
            "has_functions": has_functions,
            "has_classes": has_classes,
        }

        # 전체 통과 여부
        workflow_result["passed"] = all(check["passed"] for check in workflow_result["checks"].values())

        return workflow_result

    def validate_code(self) -> Dict[str, Any]:
        """코드 품질 검증 (문법, 정적 분석)"""
        code_result = {"passed": True, "syntax": {}, "static_analysis": {}}

        if not self.validate_file_exists():
            code_result["passed"] = False
            return code_result

        language = self.detect_language()

        # 문법 검증
        code_result["syntax"] = self._check_syntax(language)

        # 정적 분석
        code_result["static_analysis"] = self._check_static_analysis(language)

        # 전체 통과 여부
        code_result["passed"] = code_result["syntax"].get("passed", False) and code_result["static_analysis"].get(
            "passed", False
        )

        return code_result

    def _check_syntax(self, language: str) -> Dict[str, Any]:
        """문법 검사"""
        syntax_result = {"passed": True, "errors": []}

        if language == "python":
            try:
                result = subprocess.run(
                    ["python3", "-m", "py_compile", str(self.file_path)], capture_output=True, text=True, timeout=30
                )
                if result.returncode != 0:
                    syntax_result["passed"] = False
                    syntax_result["errors"] = [result.stderr.strip()]
            except FileNotFoundError:
                syntax_result["warning"] = "python3 not found"
            except subprocess.TimeoutExpired:
                syntax_result["passed"] = False
                syntax_result["errors"] = ["Syntax check timed out"]
            except Exception as e:
                syntax_result["warning"] = f"Could not run syntax check: {e}"

        elif language == "javascript":
            try:
                result = subprocess.run(
                    ["npx", "eslint", str(self.file_path), "--no-eslintrc"], capture_output=True, text=True, timeout=30
                )
                if result.returncode != 0:
                    syntax_result["passed"] = False
                    syntax_result["errors"] = [result.stdout.strip()]
            except FileNotFoundError:
                syntax_result["warning"] = "npx/eslint not found"
            except Exception as e:
                syntax_result["warning"] = f"Could not run eslint: {e}"

        else:
            syntax_result["warning"] = f"Syntax check not supported for {language}"

        return syntax_result

    def _check_static_analysis(self, language: str) -> Dict[str, Any]:
        """정적 분석"""
        analysis_result = {"passed": True, "issues": []}

        if language == "python":
            try:
                result = subprocess.run(
                    ["pylint", "--errors-only", str(self.file_path)], capture_output=True, text=True, timeout=60
                )
                if result.returncode != 0 and result.stdout.strip():
                    # pylint returns non-zero even for warnings
                    errors = [line for line in result.stdout.split("\n") if line.strip() and "error" in line.lower()]
                    if errors:
                        analysis_result["passed"] = False
                        analysis_result["issues"] = errors[:5]
            except FileNotFoundError:
                analysis_result["warning"] = "pylint not installed"
            except subprocess.TimeoutExpired:
                analysis_result["warning"] = "Static analysis timed out"
            except Exception as e:
                analysis_result["warning"] = f"Could not run pylint: {e}"

        elif language == "javascript":
            try:
                result = subprocess.run(
                    ["npx", "eslint", "--max-warnings", "0", str(self.file_path)],
                    capture_output=True,
                    text=True,
                    timeout=30,
                )
                if result.returncode != 0:
                    analysis_result["passed"] = False
                    analysis_result["issues"] = [result.stdout.strip()]
            except FileNotFoundError:
                analysis_result["warning"] = "npx/eslint not found"
            except Exception as e:
                analysis_result["warning"] = f"Could not run eslint: {e}"

        else:
            analysis_result["warning"] = f"Static analysis not supported for {language}"

        return analysis_result

    def validate_dependencies(self) -> Dict[str, Any]:
        """의존성 검사 (requirements.txt인 경우)"""
        deps_result = {"passed": True, "is_requirements": False, "vulnerabilities": [], "warnings": []}

        if not self.validate_file_exists():
            deps_result["passed"] = False
            return deps_result

        # requirements.txt 파일인지 확인
        if "requirements.txt" not in str(self.file_path):
            deps_result["note"] = "Not a requirements file - skipped"
            return deps_result

        deps_result["is_requirements"] = True

        try:
            # pip-audit 실행
            result = subprocess.run(
                ["python3", "-m", "pip_audit", "-r", str(self.file_path), "--disable-pip", "--no-deps"],
                capture_output=True,
                text=True,
                timeout=60,
            )

            full_output = result.stdout + result.stderr

            # "No known vulnerabilities" 확인
            if "No known vulnerabilities" in full_output:
                deps_result["passed"] = True
                deps_result["vulnerabilities"] = []
            else:
                # 취약점이 있거나 오류 발생
                if result.returncode != 0:
                    deps_result["passed"] = False
                    # WARNING 메시지 필터링
                    lines = [l for l in full_output.split("\n") if l.strip() and "WARNING" not in l]
                    deps_result["vulnerabilities"] = lines[:10]
                else:
                    deps_result["passed"] = True

        except FileNotFoundError:
            deps_result["warnings"].append("pip-audit not installed")
            deps_result["passed"] = False
        except subprocess.TimeoutExpired:
            deps_result["warnings"].append("Dependency scan timed out")
            deps_result["passed"] = False
        except Exception as e:
            deps_result["warnings"].append(f"Could not run pip-audit: {e}")
            deps_result["passed"] = False

        return deps_result

    def validate_security(self) -> Dict[str, Any]:
        """보안 취약점 분석"""
        security_result = {"passed": True, "vulnerabilities": [], "warnings": []}

        if not self.validate_file_exists():
            security_result["passed"] = False
            return security_result

        language = self.detect_language()

        # 기본 보안 패턴 검사
        try:
            content = self.file_path.read_text(encoding="utf-8")
        except Exception as e:
            security_result["passed"] = False
            security_result["error"] = str(e)
            return security_result

        # 위험한 패턴 검사
        dangerous_patterns = [
            (r"eval\s*\(", "Use of eval() - potential code injection"),
            (r"exec\s*\(", "Use of exec() - potential code injection"),
            (r"__import__\s*\(", "Dynamic import - potential security risk"),
            (r"subprocess\.call\s*\([^)]*shell\s*=\s*True", "Shell=True in subprocess - command injection risk"),
            (r"os\.system\s*\(", "Use of os.system - command injection risk"),
            (r"pickle\.loads?\s*\(", "Pickle can execute arbitrary code"),
            (r'password\s*=\s*["\'][^"\']+["\']', "Hardcoded password detected"),
            (r'api_key\s*=\s*["\'][^"\']+["\']', "Hardcoded API key detected"),
            (r'secret\s*=\s*["\'][^"\']+["\']', "Hardcoded secret detected"),
        ]

        for pattern, message in dangerous_patterns:
            if re.search(pattern, content, re.IGNORECASE):
                security_result["vulnerabilities"].append(message)

        # bandit 실행 (Python)
        if language == "python":
            try:
                result = subprocess.run(
                    ["bandit", "-r", "-f", "json", str(self.file_path)], capture_output=True, text=True, timeout=60
                )
                if result.returncode != 0:
                    try:
                        bandit_output = json.loads(result.stdout)
                        for issue in bandit_output.get("results", []):
                            security_result["vulnerabilities"].append(
                                f"{issue.get('issue_text', 'Unknown issue')} (severity: {issue.get('issue_severity', 'UNKNOWN')})"
                            )
                    except json.JSONDecodeError:
                        # bandit output is not JSON, might be no issues
                        pass
            except FileNotFoundError:
                security_result["warnings"].append("bandit not installed - skipping security scan")
            except subprocess.TimeoutExpired:
                security_result["warnings"].append("Security scan timed out")
            except Exception as e:
                security_result["warnings"].append(f"Could not run bandit: {e}")

        # npm audit (JavaScript)
        elif language == "javascript":
            try:
                # Check if package.json exists
                package_json = self.file_path.parent / "package.json"
                if package_json.exists():
                    result = subprocess.run(
                        ["npm", "audit", "--json"],
                        capture_output=True,
                        text=True,
                        timeout=60,
                        cwd=str(self.file_path.parent),
                    )
                    if result.returncode != 0:
                        try:
                            audit_output = json.loads(result.stdout)
                            vulnerabilities = audit_output.get("vulnerabilities", {})
                            for vuln_type, count in vulnerabilities.items():
                                if isinstance(count, dict) and count.get("total", 0) > 0:
                                    security_result["vulnerabilities"].append(
                                        f"{vuln_type}: {count['total']} vulnerabilities"
                                    )
                        except json.JSONDecodeError:
                            pass
            except FileNotFoundError:
                security_result["warnings"].append("npm not found - skipping npm audit")
            except Exception as e:
                security_result["warnings"].append(f"Could not run npm audit: {e}")

        security_result["passed"] = len(security_result["vulnerabilities"]) == 0

        return security_result

    def validate_execution(self) -> Dict[str, Any]:
        """실제 실행 테스트 - python3 <파일> test 실행 후 JSON 출력 확인"""
        exec_result = {"passed": False, "returncode": None, "output": None, "valid_json": False, "error": None}

        if not self.validate_file_exists():
            exec_result["error"] = "File not found"
            return exec_result

        language = self.detect_language()
        if language != "python":
            exec_result["passed"] = True
            exec_result["warning"] = f"Execution test only supported for Python (got: {language})"
            return exec_result

        try:
            # 절대 경로 사용으로 경로 중복 문제 해결
            abs_path = str(self.file_path.resolve())
            result = subprocess.run(["python3", abs_path, "test"], capture_output=True, text=True, timeout=15)
            exec_result["returncode"] = result.returncode
            exec_result["output"] = result.stdout.strip()[:500]

            if result.returncode != 0:
                exec_result["error"] = result.stderr.strip()[:300] or result.stdout.strip()[:300]
                return exec_result

            # JSON 출력 유효성 확인
            try:
                json.loads(result.stdout)
                exec_result["valid_json"] = True
                exec_result["passed"] = True
            except json.JSONDecodeError:
                exec_result["valid_json"] = False
                exec_result["passed"] = False
                exec_result["error"] = "Output is not valid JSON"

        except subprocess.TimeoutExpired:
            exec_result["error"] = "Execution timed out (15s)"
        except Exception as e:
            exec_result["error"] = str(e)

        return exec_result

    def run_all_validations(self) -> Dict[str, Any]:
        """모든 검증 실행"""
        try:
            log_execution(self.logger, "Starting all validations")

            if not self.validate_file_exists():
                return self.results

            # requirements.txt 파일인 경우 특별 처리
            if self.is_requirements_file():
                # 의존성 검증만 수행
                self.results["details"]["dependencies"] = self.validate_dependencies()
                self.results["details"]["workflow"] = {"passed": True, "note": "Skipped for requirements.txt"}
                self.results["details"]["code"] = {"passed": True, "note": "Skipped for requirements.txt"}
                self.results["details"]["security"] = {"passed": True, "note": "Skipped for requirements.txt"}
                self.results["details"]["execution"] = {"passed": True, "note": "Skipped for requirements.txt"}
            else:
                # 워크플로우 검증
                self.results["details"]["workflow"] = self.validate_workflow()

                # 코드 검증
                self.results["details"]["code"] = self.validate_code()

                # 보안 검증
                self.results["details"]["security"] = self.validate_security()

                # 의존성 검증 (requirements.txt인 경우)
                self.results["details"]["dependencies"] = self.validate_dependencies()

                # 실행 테스트 (신규)
                self.results["details"]["execution"] = self.validate_execution()

            # 전체 통과 여부
            self.results["all_passed"] = all(detail.get("passed", False) for detail in self.results["details"].values())

            log_execution(self.logger, "All validations completed", {"all_passed": self.results["all_passed"]})

            return self.results

        except Exception as e:
            log_error(self.logger, e, {"file": str(self.file_path)})
            self.results["error"] = str(e)
            self.results["all_passed"] = False
            return self.results

    def print_results(self, validation_type: str = "all"):
        """결과 출력"""
        if self.json_output:
            print(json.dumps(self.results, indent=2, ensure_ascii=False))
        else:
            self._print_human_readable(validation_type)

    def _print_human_readable(self, validation_type: str):
        """사람이 읽기 쉬운 형식으로 출력"""
        if "error" in self.results:
            print(f"❌ Error: {self.results['error']}")
            return

        print(f"\n📋 QC Center Validation Report")
        print(f"=" * 50)
        print(f"File: {self.file_path}")
        print(f"Type: {validation_type}")
        print(f"=" * 50)

        if "workflow" in self.results["details"]:
            self._print_workflow_results()

        if "code" in self.results["details"]:
            self._print_code_results()

        if "security" in self.results["details"]:
            self._print_security_results()

        if "dependencies" in self.results["details"]:
            self._print_dependencies_results()

        if "execution" in self.results["details"]:
            self._print_execution_results()

        # 최종 결과
        print(f"\n{'=' * 50}")
        if self.results["all_passed"]:
            print("✅ ALL CHECKS PASSED")
        else:
            print("❌ SOME CHECKS FAILED")
            print("   Review details above for issues")
        print(f"{'=' * 50}\n")

    def _print_workflow_results(self):
        """워크플로우 결과 출력"""
        workflow = self.results["details"]["workflow"]
        status = "✅" if workflow["passed"] else "❌"
        print(f"\n🔄 Workflow Validation: {status}")

        for check_name, check_result in workflow.get("checks", {}).items():
            check_status = "✅" if check_result["passed"] else "❌"
            print(f"   {check_status} {check_name}")
            if not check_result["passed"] and check_result.get("items"):
                for item in check_result["items"][:3]:
                    print(f"      - {item}")

    def _print_code_results(self):
        """코드 검증 결과 출력"""
        code = self.results["details"]["code"]
        status = "✅" if code["passed"] else "❌"
        print(f"\n💻 Code Quality: {status}")

        # Syntax
        syntax = code.get("syntax", {})
        syntax_status = "✅" if syntax.get("passed", True) else "❌"
        print(f"   {syntax_status} Syntax Check")
        if syntax.get("errors"):
            for error in syntax["errors"][:2]:
                print(f"      - {error[:100]}")
        if syntax.get("warning"):
            print(f"      ⚠️  {syntax['warning']}")

        # Static Analysis
        analysis = code.get("static_analysis", {})
        analysis_status = "✅" if analysis.get("passed", True) else "❌"
        print(f"   {analysis_status} Static Analysis")
        if analysis.get("issues"):
            for issue in analysis["issues"][:2]:
                print(f"      - {issue[:100]}")
        if analysis.get("warning"):
            print(f"      ⚠️  {analysis['warning']}")

    def _print_security_results(self):
        """보안 검증 결과 출력"""
        security = self.results["details"]["security"]
        status = "✅" if security["passed"] else "❌"
        print(f"\n🔒 Security Check: {status}")

        if security.get("vulnerabilities"):
            for vuln in security["vulnerabilities"][:5]:
                print(f"   ❌ {vuln}")

        if security.get("warnings"):
            for warning in security["warnings"][:3]:
                print(f"   ⚠️  {warning}")

    def _print_dependencies_results(self):
        """의존성 검사 결과 출력"""
        deps = self.results["details"]["dependencies"]
        status = "✅" if deps["passed"] else "❌"
        print(f"\n📦 Dependencies Check: {status}")

        if deps.get("note"):
            print(f"   ℹ️  {deps['note']}")
            return

        if deps.get("is_requirements"):
            print(f"   📄 Requirements file detected")

        if deps.get("vulnerabilities"):
            for vuln in deps["vulnerabilities"][:5]:
                print(f"   ❌ {vuln[:100]}")

        if deps.get("warnings"):
            for warning in deps["warnings"][:3]:
                print(f"   ⚠️  {warning}")

        if deps["passed"] and not deps.get("vulnerabilities"):
            print(f"   ✅ No known vulnerabilities found")

    def _print_execution_results(self):
        """실행 테스트 결과 출력"""
        execution = self.results["details"]["execution"]
        status = "✅" if execution["passed"] else "❌"
        print(f"\n🚀 Execution Test: {status}")

        if execution.get("warning"):
            print(f"   ⚠️  {execution['warning']}")
            return

        if execution.get("returncode") is not None:
            print(f"   returncode: {execution['returncode']}")

        if execution.get("valid_json"):
            print(f"   ✅ JSON output valid")

        if execution.get("output"):
            output_preview = execution["output"][:200]
            print(f"   output: {output_preview}")

        if execution.get("error"):
            print(f"   ❌ {execution['error']}")


def main():
    """메인 함수"""
    if len(sys.argv) < 3:
        print("Usage: python3 code-validator.py <type> <file_path> [--json]")
        print("Types: all, workflow, code, security, dependencies, execution")
        print("Example: python3 code-validator.py all memory/task-router.py --json")
        sys.exit(1)

    validation_type = sys.argv[1].lower()
    file_path = sys.argv[2]
    json_output = "--json" in sys.argv

    if validation_type not in ["all", "workflow", "code", "security", "dependencies", "execution"]:
        print(f"Error: Unknown validation type '{validation_type}'")
        print("Valid types: all, workflow, code, security, dependencies, execution")
        sys.exit(1)

    validator = QCValidator(file_path, json_output)

    if validation_type == "all":
        validator.run_all_validations()
    elif validation_type == "workflow":
        if validator.validate_file_exists():
            validator.results["details"]["workflow"] = validator.validate_workflow()
            validator.results["all_passed"] = validator.results["details"]["workflow"]["passed"]
    elif validation_type == "code":
        if validator.validate_file_exists():
            validator.results["details"]["code"] = validator.validate_code()
            validator.results["all_passed"] = validator.results["details"]["code"]["passed"]
    elif validation_type == "security":
        if validator.validate_file_exists():
            validator.results["details"]["security"] = validator.validate_security()
            validator.results["all_passed"] = validator.results["details"]["security"]["passed"]
    elif validation_type == "dependencies":
        if validator.validate_file_exists():
            validator.results["details"]["dependencies"] = validator.validate_dependencies()
            validator.results["all_passed"] = validator.results["details"]["dependencies"]["passed"]
    elif validation_type == "execution":
        if validator.validate_file_exists():
            validator.results["details"]["execution"] = validator.validate_execution()
            validator.results["all_passed"] = validator.results["details"]["execution"]["passed"]

    validator.print_results(validation_type)

    # Exit code for CI/CD
    sys.exit(0 if validator.results["all_passed"] else 1)


if __name__ == "__main__":
    main()
