import json
import os
import subprocess
from datetime import datetime
from pathlib import Path
from typing import Optional

import psutil

from mosaygent.logger import get_logger

logger = get_logger(__name__)


class EnvironmentService:
    """Manages project directory and codebase detection."""

    def __init__(self):
        self.project_dir: Optional[Path] = None
        self.config_file = Path.home() / ".mosayic" / "config.txt"
        self._load_project_dir()

    def _load_project_dir(self):
        """Load project directory from config file."""
        if self.config_file.exists():
            try:
                dir_path = self.config_file.read_text().strip()
                if dir_path and Path(dir_path).exists():
                    self.project_dir = Path(dir_path)
                    logger.info(f"Loaded project directory: {self.project_dir}")
            except Exception as e:
                logger.error(f"Failed to load project directory: {e}")

    def set_project_dir(self, path: str) -> bool:
        """Set and persist the project directory."""
        logger.info(f"set_project_dir called with path: {path}, current project_dir: {self.project_dir}")

        dir_path = Path(path)
        if not dir_path.exists():
            raise FileNotFoundError(f"Directory does not exist: {path}")

        if not dir_path.is_dir():
            raise NotADirectoryError(f"Path is not a directory: {path}")

        self.project_dir = dir_path

        # Save to config
        self.config_file.parent.mkdir(parents=True, exist_ok=True)
        self.config_file.write_text(str(dir_path))
        logger.info(f"Successfully updated project directory to: {self.project_dir}")
        return True

    def get_project_dir(self) -> Optional[str]:
        """Get the current project directory."""
        return str(self.project_dir) if self.project_dir else None

    def check_codebase_status(self, codebase_type: str) -> dict:
        """Check status of a specific codebase (api or mobile)."""
        if not self.project_dir:
            logger.warning("check_codebase_status called but project_dir is not set")
            return {"error": "Project directory not set"}

        logger.info(f"Checking {codebase_type} codebase status in project_dir: {self.project_dir}")

        if codebase_type == "api":
            codebase_dir = self._find_python_codebase()
            marker_file = "pyproject.toml"
        elif codebase_type == "mobile":
            codebase_dir = self._find_react_native_codebase()
            marker_file = "app.json"
        else:
            return {"error": "Invalid codebase type"}

        if not codebase_dir:
            result = {
                "detected": False,
                "path": None,
                "repository_url": None,
                "git": None,
                "running": False,
                "dependencies": False,
                "last_modified": None,
            }
            if codebase_type == "mobile":
                result["bundle_id"] = None
            return result

        status = {
            "detected": False,
            "path": str(codebase_dir),
            "repository_url": None,
            "git": None,
            "running": False,
            "dependencies": False,
            "last_modified": None,
        }

        # Check if directory and marker file exist
        if not codebase_dir.exists() or not (codebase_dir / marker_file).exists():
            if codebase_type == "mobile":
                status["bundle_id"] = None
            return status

        status["detected"] = True

        # Check git status
        status["git"] = self._check_git_status(codebase_dir)

        # Extract repository URL from git status if available
        if status["git"] and status["git"].get("remote", {}).get("has_remote"):
            status["repository_url"] = status["git"]["remote"]["remote_url"]
            logger.info(f"Repository URL for {codebase_type}: {status['repository_url']}")

        # Check if running
        if codebase_type == "api":
            status["running"] = self._check_port_in_use(8080)
        elif codebase_type == "mobile":
            status["running"] = self._check_port_in_use(8081)

        # Check dependencies
        if codebase_type == "api":
            status["dependencies"] = (codebase_dir / ".venv").exists()
        elif codebase_type == "mobile":
            status["dependencies"] = (codebase_dir / "node_modules").exists()
            status["bundle_id"] = self._get_bundle_id(codebase_dir)

        # Get last modified time
        status["last_modified"] = self._get_last_modified(codebase_dir)

        return status

    def _find_python_codebase(self) -> Optional[Path]:
        """Find a valid Python codebase, excluding internal tooling directories."""
        if not self.project_dir:
            return None

        excluded_dirs = ["pymosayic", "pymosaygent"]

        logger.info(f"Searching for Python codebase in {self.project_dir}")

        for subdir in sorted(self.project_dir.iterdir()):
            if not subdir.is_dir() or subdir.name in excluded_dirs or subdir.name.startswith('.'):
                continue

            if (subdir / "pyproject.toml").exists():
                logger.info(f"Found Python codebase: {subdir}")
                return subdir

        logger.info(f"No valid Python codebase found in {self.project_dir}")
        return None

    def _find_react_native_codebase(self) -> Optional[Path]:
        """Find a valid React Native/Expo codebase by searching for app.json with expo config."""
        if not self.project_dir:
            return None

        logger.info(f"Searching for React Native codebase in {self.project_dir}")

        for subdir in sorted(self.project_dir.iterdir()):
            if not subdir.is_dir() or subdir.name.startswith('.'):
                continue

            app_json = subdir / "app.json"
            if app_json.exists():
                logger.info(f"Found React Native codebase: {subdir}")
                return subdir

        logger.info(f"No valid React Native codebase found in {self.project_dir}")
        return None

    def _get_bundle_id(self, mobile_dir: Path) -> Optional[str]:
        """Extract the Android package (bundle ID) from app.json."""
        app_json_path = mobile_dir / "app.json"

        if not app_json_path.exists():
            logger.debug(f"app.json not found at {app_json_path}")
            return None

        try:
            with open(app_json_path) as f:
                app_config = json.load(f)

            bundle_id = app_config.get("expo", {}).get("android", {}).get("package")

            if bundle_id:
                logger.info(f"Found bundle ID: {bundle_id}")
                return bundle_id

            logger.debug("No bundle ID found in app.json")
            return None
        except json.JSONDecodeError as e:
            logger.warning(f"Failed to parse app.json: {e}")
            return None
        except Exception as e:
            logger.warning(f"Error reading app.json: {e}")
            return None

    def _get_git_remote_info(self, repo_dir: Path) -> dict:
        """Get git remote information for a repository.

        Args:
            repo_dir: Path to the git repository

        Returns:
            Dict with has_remote (bool) and remote_url (str or None)
        """
        logger.info(f"Checking git remote for repository: {repo_dir}")

        try:
            result = subprocess.run(
                ["git", "remote", "get-url", "origin"],
                cwd=repo_dir,
                capture_output=True,
                text=True,
                timeout=5
            )

            if result.returncode == 0:
                remote_url = result.stdout.strip()
                logger.info(f"Found git remote: {remote_url}")
                return {
                    "has_remote": True,
                    "remote_url": remote_url
                }

            logger.info("No git remote configured")
            return {
                "has_remote": False,
                "remote_url": None
            }
        except Exception as e:
            logger.warning(f"Error checking git remote: {e}")
            return {
                "has_remote": False,
                "remote_url": None
            }

    def _check_git_status(self, repo_dir: Path) -> Optional[dict]:
        """Check git status of a repository."""
        if not (repo_dir / ".git").exists():
            return None

        try:
            # Get current branch
            result = subprocess.run(
                ["git", "rev-parse", "--abbrev-ref", "HEAD"],
                cwd=repo_dir,
                capture_output=True,
                text=True,
                timeout=5
            )
            branch = result.stdout.strip() if result.returncode == 0 else "unknown"

            # Check if dirty
            result = subprocess.run(
                ["git", "status", "--porcelain"],
                cwd=repo_dir,
                capture_output=True,
                text=True,
                timeout=5
            )
            is_dirty = bool(result.stdout.strip()) if result.returncode == 0 else False

            # Get remote info
            remote_info = self._get_git_remote_info(repo_dir)

            return {
                "branch": branch,
                "dirty": is_dirty,
                "remote": remote_info,
            }
        except Exception as e:
            logger.debug(f"Failed to check git status: {e}")
            return None

    def _check_port_in_use(self, port: int) -> bool:
        """Check if a port is in use."""
        try:
            for conn in psutil.net_connections():
                if conn.laddr.port == port and conn.status == 'LISTEN':
                    return True
            return False
        except Exception:
            return False

    def _get_last_modified(self, directory: Path) -> Optional[str]:
        """Get the last modified time of files in a directory."""
        try:
            latest_time = 0
            for root, dirs, files in os.walk(directory):
                # Skip hidden and common excluded directories
                dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['node_modules', '__pycache__']]

                for file in files:
                    if not file.startswith('.'):
                        file_path = Path(root) / file
                        try:
                            mtime = file_path.stat().st_mtime
                            latest_time = max(latest_time, mtime)
                        except Exception:
                            continue

            if latest_time > 0:
                dt = datetime.fromtimestamp(latest_time)
                return dt.isoformat()
            return None
        except Exception as e:
            logger.debug(f"Failed to get last modified time: {e}")
            return None

