"""
Project scanner for generating aicippy.md files.

Analyzes project structure, detects tech stack, and
generates context files for AI agents.
"""

from __future__ import annotations

import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from aicippy.utils.logging import get_logger

logger = get_logger(__name__)


@dataclass
class ProjectInfo:
    """Information about a scanned project."""

    name: str
    path: Path
    tech_stack: list[str] = field(default_factory=list)
    languages: list[str] = field(default_factory=list)
    frameworks: list[str] = field(default_factory=list)
    package_managers: list[str] = field(default_factory=list)
    directories: list[str] = field(default_factory=list)
    key_files: list[str] = field(default_factory=list)
    files_count: int = 0
    description: str = ""
    conventions: list[str] = field(default_factory=list)
    build_commands: dict[str, str] = field(default_factory=dict)
    test_commands: dict[str, str] = field(default_factory=dict)

    def to_markdown(self) -> str:
        """Generate markdown content for aicippy.md."""
        md = f"# {self.name}\n\n"
        md += f"Project path: `{self.path}`\n\n"

        if self.description:
            md += f"## Description\n\n{self.description}\n\n"

        md += "## Tech Stack\n\n"
        for tech in self.tech_stack:
            md += f"- {tech}\n"
        md += "\n"

        md += "## Languages\n\n"
        for lang in self.languages:
            md += f"- {lang}\n"
        md += "\n"

        if self.frameworks:
            md += "## Frameworks\n\n"
            for framework in self.frameworks:
                md += f"- {framework}\n"
            md += "\n"

        md += "## Project Structure\n\n"
        md += "Key directories:\n"
        for directory in self.directories[:20]:
            md += f"- `{directory}/`\n"
        md += "\n"

        md += "Key files:\n"
        for file in self.key_files[:20]:
            md += f"- `{file}`\n"
        md += "\n"

        if self.build_commands:
            md += "## Build Commands\n\n"
            for name, cmd in self.build_commands.items():
                md += f"- **{name}**: `{cmd}`\n"
            md += "\n"

        if self.test_commands:
            md += "## Test Commands\n\n"
            for name, cmd in self.test_commands.items():
                md += f"- **{name}**: `{cmd}`\n"
            md += "\n"

        if self.conventions:
            md += "## Conventions\n\n"
            for convention in self.conventions:
                md += f"- {convention}\n"
            md += "\n"

        md += f"\n---\n*Generated by AiCippy | Files scanned: {self.files_count}*\n"

        return md


# File patterns for tech stack detection
TECH_PATTERNS: dict[str, dict[str, Any]] = {
    # Python
    "pyproject.toml": {"lang": "Python", "pkg": "pip/poetry/hatch"},
    "setup.py": {"lang": "Python", "pkg": "pip"},
    "requirements.txt": {"lang": "Python", "pkg": "pip"},
    "Pipfile": {"lang": "Python", "pkg": "pipenv"},
    "poetry.lock": {"lang": "Python", "pkg": "poetry"},
    # JavaScript/TypeScript
    "package.json": {"lang": "JavaScript/TypeScript", "pkg": "npm/yarn/pnpm"},
    "yarn.lock": {"pkg": "yarn"},
    "pnpm-lock.yaml": {"pkg": "pnpm"},
    "package-lock.json": {"pkg": "npm"},
    "bun.lockb": {"pkg": "bun"},
    "tsconfig.json": {"lang": "TypeScript"},
    # Rust
    "Cargo.toml": {"lang": "Rust", "pkg": "cargo"},
    # Go
    "go.mod": {"lang": "Go", "pkg": "go mod"},
    # Java/Kotlin
    "pom.xml": {"lang": "Java", "pkg": "maven"},
    "build.gradle": {"lang": "Java/Kotlin", "pkg": "gradle"},
    "build.gradle.kts": {"lang": "Kotlin", "pkg": "gradle"},
    # .NET
    "*.csproj": {"lang": "C#", "pkg": "nuget"},
    "*.fsproj": {"lang": "F#", "pkg": "nuget"},
    # Ruby
    "Gemfile": {"lang": "Ruby", "pkg": "bundler"},
    # PHP
    "composer.json": {"lang": "PHP", "pkg": "composer"},
    # Mobile
    "pubspec.yaml": {"lang": "Dart", "framework": "Flutter"},
    "app.json": {"framework": "Expo/React Native"},
    "ios/": {"platform": "iOS"},
    "android/": {"platform": "Android"},
    # Infrastructure
    "cdk.json": {"framework": "AWS CDK"},
    "serverless.yml": {"framework": "Serverless Framework"},
    "terraform/": {"framework": "Terraform"},
    "Dockerfile": {"tech": "Docker"},
    "docker-compose.yml": {"tech": "Docker Compose"},
    "kubernetes/": {"tech": "Kubernetes"},
    ".github/workflows/": {"tech": "GitHub Actions"},
}

# Framework detection from package.json dependencies
FRAMEWORK_DEPS: dict[str, str] = {
    "react": "React",
    "react-native": "React Native",
    "vue": "Vue.js",
    "angular": "Angular",
    "svelte": "Svelte",
    "next": "Next.js",
    "nuxt": "Nuxt.js",
    "express": "Express.js",
    "fastify": "Fastify",
    "nestjs": "NestJS",
    "@aws-cdk/core": "AWS CDK",
    "aws-cdk-lib": "AWS CDK",
    "expo": "Expo",
}


class ProjectScanner:
    """
    Scanner for analyzing project structure and tech stack.
    """

    def __init__(self, path: Path | str) -> None:
        """
        Initialize the scanner.

        Args:
            path: Path to the project root.
        """
        self.path = Path(path).resolve()
        self._files_count = 0

    def scan(self) -> ProjectInfo:
        """
        Scan the project and extract information.

        Returns:
            ProjectInfo with detected configuration.
        """
        logger.info("scanning_project", path=str(self.path))

        info = ProjectInfo(
            name=self.path.name,
            path=self.path,
        )

        # Scan for tech indicators
        self._scan_files(info)

        # Detect frameworks from package.json
        self._detect_package_json_frameworks(info)

        # Extract commands from config files
        self._extract_commands(info)

        # Add conventions based on detected stack
        self._infer_conventions(info)

        info.files_count = self._files_count

        # Deduplicate lists
        info.tech_stack = list(dict.fromkeys(info.tech_stack))
        info.languages = list(dict.fromkeys(info.languages))
        info.frameworks = list(dict.fromkeys(info.frameworks))
        info.package_managers = list(dict.fromkeys(info.package_managers))

        logger.info(
            "scan_completed",
            files=self._files_count,
            languages=len(info.languages),
            frameworks=len(info.frameworks),
        )

        return info

    def _scan_files(self, info: ProjectInfo) -> None:
        """Scan directory for tech indicators."""
        ignore_dirs = {".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build"}

        for root, dirs, files in os.walk(self.path):
            # Filter ignored directories
            dirs[:] = [d for d in dirs if d not in ignore_dirs and not d.startswith(".")]

            rel_root = Path(root).relative_to(self.path)

            # Track directories
            if str(rel_root) != ".":
                info.directories.append(str(rel_root))

            for file in files:
                self._files_count += 1

                # Check against patterns
                for pattern, detection in TECH_PATTERNS.items():
                    if file == pattern or (pattern.endswith("/") and file == pattern[:-1]):
                        if "lang" in detection:
                            info.languages.append(detection["lang"])
                            info.tech_stack.append(detection["lang"])
                        if "pkg" in detection:
                            info.package_managers.append(detection["pkg"])
                        if "framework" in detection:
                            info.frameworks.append(detection["framework"])
                            info.tech_stack.append(detection["framework"])
                        if "tech" in detection:
                            info.tech_stack.append(detection["tech"])
                        if "platform" in detection:
                            info.tech_stack.append(detection["platform"])

                # Track key files
                if file in (
                    "README.md",
                    "CLAUDE.md",
                    "aicippy.md",
                    "pyproject.toml",
                    "package.json",
                    "Dockerfile",
                    "Makefile",
                ):
                    info.key_files.append(str(rel_root / file))

    def _detect_package_json_frameworks(self, info: ProjectInfo) -> None:
        """Detect frameworks from package.json."""
        package_json = self.path / "package.json"
        if not package_json.exists():
            return

        try:
            import json
            with open(package_json) as f:
                data = json.load(f)

            # Check dependencies
            all_deps = {}
            all_deps.update(data.get("dependencies", {}))
            all_deps.update(data.get("devDependencies", {}))

            for dep, framework in FRAMEWORK_DEPS.items():
                if dep in all_deps:
                    info.frameworks.append(framework)
                    info.tech_stack.append(framework)

            # Get description
            if "description" in data:
                info.description = data["description"]

        except Exception as e:
            logger.warning("package_json_parse_failed", error=str(e))

    def _extract_commands(self, info: ProjectInfo) -> None:
        """Extract build and test commands from config files."""
        # From package.json
        package_json = self.path / "package.json"
        if package_json.exists():
            try:
                import json
                with open(package_json) as f:
                    data = json.load(f)

                scripts = data.get("scripts", {})
                if "build" in scripts:
                    info.build_commands["npm build"] = f"npm run build"
                if "test" in scripts:
                    info.test_commands["npm test"] = f"npm test"
                if "dev" in scripts:
                    info.build_commands["npm dev"] = f"npm run dev"
            except Exception:
                pass

        # From pyproject.toml
        pyproject = self.path / "pyproject.toml"
        if pyproject.exists():
            info.build_commands["pip install"] = "pip install -e ."
            info.test_commands["pytest"] = "pytest"

        # From Makefile
        makefile = self.path / "Makefile"
        if makefile.exists():
            info.build_commands["make"] = "make"
            info.test_commands["make test"] = "make test"

    def _infer_conventions(self, info: ProjectInfo) -> None:
        """Infer coding conventions from detected stack."""
        conventions = []

        if "TypeScript" in info.languages:
            conventions.append("Use TypeScript strict mode")

        if "Python" in info.languages:
            conventions.append("Follow PEP 8 style guide")
            conventions.append("Use type hints")

        if "React" in info.frameworks:
            conventions.append("Use functional components with hooks")

        if "AWS CDK" in info.tech_stack:
            conventions.append("Use CDK best practices for resource naming")

        if "Docker" in info.tech_stack:
            conventions.append("Use multi-stage builds for smaller images")

        info.conventions = conventions
