"""
Code quality checker for validating generated code
"""

import ast
import re
from pathlib import Path
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
import pycodestyle


@dataclass
class QualityIssue:
    """
    Code quality issue

    Attributes:
        severity: Issue severity ('error' | 'warning' | 'info')
        category: Issue category ('style' | 'syntax' | 'documentation' | 'imports')
        line_number: Line number where issue occurs
        column: Column number where issue occurs
        message: Issue description
        code: Error/warning code (if applicable)
    """

    severity: str  # 'error' | 'warning' | 'info'
    category: str  # 'style' | 'syntax' | 'documentation' | 'imports'
    line_number: int
    column: int
    message: str
    code: Optional[str] = None


@dataclass
class QualityReport:
    """
    Code quality report

    Attributes:
        file_path: Path to checked file
        passed: Whether all checks passed
        issues: List of quality issues
        pep8_compliant: Whether code is PEP8 compliant
        has_docstrings: Whether code has proper docstrings
        syntax_valid: Whether code has valid syntax
        imports_valid: Whether imports are valid
        score: Quality score (0-100)
    """

    file_path: str
    passed: bool
    issues: List[QualityIssue] = field(default_factory=list)
    pep8_compliant: bool = True
    has_docstrings: bool = True
    syntax_valid: bool = True
    imports_valid: bool = True
    score: float = 100.0


class CodeQualityChecker:
    """
    Code quality checker for validating Python code

    This class performs various quality checks including PEP8 compliance,
    docstring presence, syntax validation, and import checking.
    """

    def __init__(self, strict_mode: bool = False):
        """
        Initialize code quality checker

        Args:
            strict_mode: If True, treat warnings as errors
        """
        self.strict_mode = strict_mode
        self.pep8_checker = pycodestyle.StyleGuide(quiet=True)

    def check_file(self, file_path: str) -> QualityReport:
        """
        Perform all quality checks on a file

        Args:
            file_path: Path to file to check

        Returns:
            Quality report with all check results
        """
        path = Path(file_path)
        if not path.exists():
            return QualityReport(
                file_path=file_path,
                passed=False,
                syntax_valid=False,
                issues=[
                    QualityIssue(
                        severity="error",
                        category="file",
                        line_number=0,
                        column=0,
                        message=f"File not found: {file_path}",
                    )
                ],
            )

        with open(path, "r", encoding="utf-8") as f:
            code = f.read()

        return self.check_code(code, file_path)

    def check_code(self, code: str, file_path: str = "<string>") -> QualityReport:
        """
        Perform all quality checks on code string

        Args:
            code: Code to check
            file_path: File path for reporting

        Returns:
            Quality report with all check results
        """
        report = QualityReport(file_path=file_path, passed=True)

        # Check syntax
        syntax_issues = self.check_syntax(code)
        report.issues.extend(syntax_issues)
        report.syntax_valid = len(syntax_issues) == 0

        # If syntax is invalid, skip other checks
        if not report.syntax_valid:
            report.passed = False
            return report

        # Check PEP8
        pep8_issues = self.check_pep8(file_path, code)
        report.issues.extend(pep8_issues)
        report.pep8_compliant = len(pep8_issues) == 0

        # Check docstrings
        docstring_issues = self.check_docstrings(code)
        report.issues.extend(docstring_issues)
        report.has_docstrings = len(docstring_issues) == 0

        # Check imports
        import_issues = self.check_imports(code)
        report.issues.extend(import_issues)
        report.imports_valid = len(import_issues) == 0

        # Calculate score
        report.score = self._calculate_score(report.issues)

        # Determine if passed
        error_count = sum(1 for issue in report.issues if issue.severity == "error")
        if self.strict_mode:
            report.passed = len(report.issues) == 0
        else:
            report.passed = error_count == 0

        return report

    def check_pep8(self, file_path: str, code: Optional[str] = None) -> List[QualityIssue]:
        """
        Check PEP8 compliance

        Args:
            file_path: Path to file to check
            code: Optional code string (if not provided, reads from file)

        Returns:
            List of PEP8 violations
        """
        issues = []

        if code is not None:
            # Write code to temporary file for checking
            import tempfile

            with tempfile.NamedTemporaryFile(
                mode="w", suffix=".py", delete=False, encoding="utf-8"
            ) as f:
                f.write(code)
                temp_path = f.name

            try:
                result = self.pep8_checker.check_files([temp_path])
                # Parse results from the result object
                if hasattr(result, "messages"):
                    for msg in result.messages:
                        issues.append(
                            QualityIssue(
                                severity="warning",
                                category="style",
                                line_number=msg.get("line", 0),
                                column=msg.get("column", 0),
                                message=msg.get("message", ""),
                                code=msg.get("code", ""),
                            )
                        )
                # Alternative: use total_errors count
                elif result.total_errors > 0:
                    # If we can't get detailed errors, just note that there are issues
                    issues.append(
                        QualityIssue(
                            severity="warning",
                            category="style",
                            line_number=0,
                            column=0,
                            message=f"Found {result.total_errors} PEP8 style issues",
                            code="E000",
                        )
                    )
            finally:
                Path(temp_path).unlink()
        else:
            result = self.pep8_checker.check_files([file_path])
            if hasattr(result, "messages"):
                for msg in result.messages:
                    issues.append(
                        QualityIssue(
                            severity="warning",
                            category="style",
                            line_number=msg.get("line", 0),
                            column=msg.get("column", 0),
                            message=msg.get("message", ""),
                            code=msg.get("code", ""),
                        )
                    )
            elif result.total_errors > 0:
                issues.append(
                    QualityIssue(
                        severity="warning",
                        category="style",
                        line_number=0,
                        column=0,
                        message=f"Found {result.total_errors} PEP8 style issues",
                        code="E000",
                    )
                )

        return issues

    def check_docstrings(self, code: str) -> List[QualityIssue]:
        """
        Check docstring presence and quality

        Args:
            code: Code to check

        Returns:
            List of docstring issues
        """
        issues = []

        try:
            tree = ast.parse(code)

            # Check module docstring
            if not ast.get_docstring(tree):
                issues.append(
                    QualityIssue(
                        severity="warning",
                        category="documentation",
                        line_number=1,
                        column=0,
                        message="Missing module docstring",
                    )
                )

            # Check class and function docstrings
            for node in ast.walk(tree):
                if isinstance(node, ast.ClassDef):
                    if not ast.get_docstring(node):
                        issues.append(
                            QualityIssue(
                                severity="warning",
                                category="documentation",
                                line_number=node.lineno,
                                column=node.col_offset,
                                message=f"Class '{node.name}' missing docstring",
                            )
                        )
                elif isinstance(node, ast.FunctionDef):
                    # Skip private functions (starting with _)
                    if not node.name.startswith("_"):
                        if not ast.get_docstring(node):
                            issues.append(
                                QualityIssue(
                                    severity="warning",
                                    category="documentation",
                                    line_number=node.lineno,
                                    column=node.col_offset,
                                    message=f"Function '{node.name}' missing docstring",
                                )
                            )

        except SyntaxError:
            # Syntax errors are handled by check_syntax
            pass

        return issues

    def check_syntax(self, code: str) -> List[QualityIssue]:
        """
        Check Python syntax

        Args:
            code: Code to check

        Returns:
            List of syntax errors
        """
        issues = []

        try:
            ast.parse(code)
        except SyntaxError as e:
            issues.append(
                QualityIssue(
                    severity="error",
                    category="syntax",
                    line_number=e.lineno or 0,
                    column=e.offset or 0,
                    message=f"Syntax error: {e.msg}",
                )
            )

        return issues

    def check_imports(self, code: str) -> List[QualityIssue]:
        """
        Check import statements

        Args:
            code: Code to check

        Returns:
            List of import issues
        """
        issues = []

        try:
            tree = ast.parse(code)

            # Track imports
            imports = []
            import_lines = {}

            for node in ast.walk(tree):
                if isinstance(node, ast.Import):
                    for alias in node.names:
                        imports.append(alias.name)
                        import_lines[alias.name] = node.lineno
                elif isinstance(node, ast.ImportFrom):
                    if node.module:
                        imports.append(node.module)
                        import_lines[node.module] = node.lineno

            # Check for duplicate imports
            seen = set()
            for imp in imports:
                if imp in seen:
                    issues.append(
                        QualityIssue(
                            severity="warning",
                            category="imports",
                            line_number=import_lines.get(imp, 0),
                            column=0,
                            message=f"Duplicate import: {imp}",
                        )
                    )
                seen.add(imp)

            # Check import order (stdlib, third-party, local)
            # This is a simplified check
            lines = code.split("\n")
            import_section = []
            for i, line in enumerate(lines):
                if line.strip().startswith(("import ", "from ")):
                    import_section.append((i + 1, line))

            # Check if imports are grouped
            if len(import_section) > 1:
                prev_type = self._get_import_type(import_section[0][1])
                for line_no, line in import_section[1:]:
                    curr_type = self._get_import_type(line)
                    if curr_type < prev_type:
                        issues.append(
                            QualityIssue(
                                severity="info",
                                category="imports",
                                line_number=line_no,
                                column=0,
                                message="Imports not properly grouped (stdlib, third-party, local)",
                            )
                        )
                        break
                    prev_type = curr_type

        except SyntaxError:
            # Syntax errors are handled by check_syntax
            pass

        return issues

    def _get_import_type(self, import_line: str) -> int:
        """
        Determine import type for ordering

        Args:
            import_line: Import statement line

        Returns:
            0 for stdlib, 1 for third-party, 2 for local
        """
        # Extract module name
        match = re.match(r"(?:from|import)\s+([a-zA-Z0-9_]+)", import_line)
        if not match:
            return 2

        module = match.group(1)

        # Standard library modules (partial list)
        stdlib_modules = {
            "os",
            "sys",
            "re",
            "json",
            "time",
            "datetime",
            "pathlib",
            "typing",
            "dataclasses",
            "collections",
            "itertools",
            "functools",
            "abc",
            "ast",
            "io",
            "logging",
            "threading",
            "multiprocessing",
            "subprocess",
            "tempfile",
            "shutil",
            "glob",
            "pickle",
            "csv",
            "sqlite3",
            "http",
            "urllib",
            "email",
            "unittest",
        }

        if module in stdlib_modules:
            return 0

        # Local imports (starting with .)
        if import_line.strip().startswith("from ."):
            return 2

        # Third-party
        return 1

    def _calculate_score(self, issues: List[QualityIssue]) -> float:
        """
        Calculate quality score based on issues

        Args:
            issues: List of quality issues

        Returns:
            Score from 0 to 100
        """
        if not issues:
            return 100.0

        # Weight by severity
        penalty = 0
        for issue in issues:
            if issue.severity == "error":
                penalty += 10
            elif issue.severity == "warning":
                penalty += 2
            elif issue.severity == "info":
                penalty += 0.5

        score = max(0.0, 100.0 - penalty)
        return score

    def format_report(self, report: QualityReport) -> str:
        """
        Format quality report as human-readable string

        Args:
            report: Quality report

        Returns:
            Formatted report string
        """
        lines = []
        lines.append(f"Quality Report: {report.file_path}")
        lines.append("=" * 60)
        lines.append(f"Score: {report.score:.1f}/100")
        lines.append(f"Status: {'PASSED' if report.passed else 'FAILED'}")
        lines.append("")
        lines.append(f"PEP8 Compliant: {'✓' if report.pep8_compliant else '✗'}")
        lines.append(f"Has Docstrings: {'✓' if report.has_docstrings else '✗'}")
        lines.append(f"Syntax Valid: {'✓' if report.syntax_valid else '✗'}")
        lines.append(f"Imports Valid: {'✓' if report.imports_valid else '✗'}")

        if report.issues:
            lines.append("")
            lines.append(f"Issues ({len(report.issues)}):")
            lines.append("-" * 60)

            # Group by severity
            errors = [i for i in report.issues if i.severity == "error"]
            warnings = [i for i in report.issues if i.severity == "warning"]
            infos = [i for i in report.issues if i.severity == "info"]

            for severity, issues_list in [
                ("ERRORS", errors),
                ("WARNINGS", warnings),
                ("INFO", infos),
            ]:
                if issues_list:
                    lines.append(f"\n{severity}:")
                    for issue in issues_list:
                        lines.append(
                            f"  Line {issue.line_number}:{issue.column} "
                            f"[{issue.category}] {issue.message}"
                        )

        return "\n".join(lines)
