"""
Installation and configuration logic for serena-vbnet.
"""

import os
import platform
import shutil
import subprocess
import sys
import tarfile
import tempfile
import zipfile
from pathlib import Path
from typing import List, Dict, Any
import requests

from . import __version__

# Installation paths
SERENA_DIR = Path.home() / ".serena"
LS_DIR = SERENA_DIR / "language_servers" / "roslyn_vbnet"

# GitHub repository for pre-built binaries
GITHUB_REPO = "LaunchCG/roslyn-vbnet-languageserver"
GITHUB_API = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"


def get_platform_rid() -> str:
    """Detect platform runtime identifier (RID)"""
    system = platform.system().lower()
    machine = platform.machine().lower()

    # Map to .NET RIDs
    if system == "linux":
        if machine in ["x86_64", "amd64"]:
            return "linux-x64"
        elif machine in ["aarch64", "arm64"]:
            return "linux-arm64"
    elif system == "darwin":
        if machine == "x86_64":
            return "osx-x64"
        elif machine in ["arm64", "aarch64"]:
            return "osx-arm64"
    elif system == "windows":
        if machine in ["amd64", "x86_64"]:
            return "win-x64"
        elif machine in ["arm64", "aarch64"]:
            return "win-arm64"

    raise RuntimeError(f"Unsupported platform: {system}/{machine}")


def download_prebuilt_binary() -> Path:
    """Download pre-built binary from GitHub Releases"""
    rid = get_platform_rid()
    print(f"Detected platform: {rid}")

    # Get latest release info
    print(f"Fetching latest release from {GITHUB_REPO}...")
    response = requests.get(GITHUB_API)
    response.raise_for_status()

    release_data = response.json()
    tag_name = release_data["tag_name"]
    print(f"Latest version: {tag_name}")

    # Find asset for this platform
    ext = "zip" if "win" in rid else "tar.gz"
    asset_name = f"roslyn-vbnet-{rid}.{ext}"

    asset = None
    for a in release_data["assets"]:
        if a["name"] == asset_name:
            asset = a
            break

    if not asset:
        raise RuntimeError(f"No pre-built binary found for {rid}")

    # Download
    download_url = asset["browser_download_url"]
    print(f"Downloading {asset_name}...")

    response = requests.get(download_url, stream=True)
    response.raise_for_status()

    # Save to temp file
    temp_file = Path(tempfile.gettempdir()) / asset_name
    with open(temp_file, "wb") as f:
        for chunk in response.iter_content(chunk_size=8192):
            f.write(chunk)

    print(f"Downloaded to {temp_file}")
    return temp_file


def extract_binary(archive_path: Path, dest_dir: Path):
    """Extract downloaded binary to destination"""
    print(f"Extracting to {dest_dir}...")

    # Create destination
    dest_dir.mkdir(parents=True, exist_ok=True)

    # Extract based on file type
    if archive_path.suffix == ".zip":
        with zipfile.ZipFile(archive_path, "r") as zip_ref:
            zip_ref.extractall(dest_dir)
    else:  # .tar.gz
        with tarfile.open(archive_path, "r:gz") as tar_ref:
            tar_ref.extractall(dest_dir)

    # Handle nested directory structure (e.g., LanguageServer subdirectory)
    # If extraction created a single subdirectory containing the DLLs, flatten it
    _flatten_single_subdirectory(dest_dir)

    print("[OK] Extraction complete")


def _flatten_single_subdirectory(dest_dir: Path):
    """
    If dest_dir contains a single subdirectory with the actual files,
    move everything up one level and remove the empty subdirectory.
    """
    items = list(dest_dir.iterdir())

    # Check if there's a LanguageServer subdirectory or similar containing the DLLs
    language_server_dir = dest_dir / "LanguageServer"
    if language_server_dir.exists() and language_server_dir.is_dir():
        main_dll = language_server_dir / "Microsoft.CodeAnalysis.LanguageServer.dll"
        if main_dll.exists():
            print("Flattening LanguageServer subdirectory...")
            # Move all files from LanguageServer to dest_dir
            for item in language_server_dir.iterdir():
                target = dest_dir / item.name
                if target.exists():
                    if target.is_dir():
                        shutil.rmtree(target)
                    else:
                        target.unlink()
                shutil.move(str(item), str(target))
            # Remove the now-empty LanguageServer directory
            language_server_dir.rmdir()
            return

    # Generic case: if there's exactly one subdirectory and no files at root level
    # (except maybe VERSION.txt or similar metadata), flatten it
    subdirs = [item for item in items if item.is_dir()]
    files = [item for item in items if item.is_file()]

    if len(subdirs) == 1 and len(files) <= 1:
        subdir = subdirs[0]
        # Check if the subdir contains the main DLL
        main_dll = subdir / "Microsoft.CodeAnalysis.LanguageServer.dll"
        if main_dll.exists():
            print(f"Flattening {subdir.name} subdirectory...")
            # Move all files from subdir to dest_dir
            for item in subdir.iterdir():
                target = dest_dir / item.name
                if target.exists():
                    if target.is_dir():
                        shutil.rmtree(target)
                    else:
                        target.unlink()
                shutil.move(str(item), str(target))
            # Remove the now-empty subdirectory
            subdir.rmdir()


def build_from_source():
    """Build Roslyn LanguageServer from source"""
    print("Building from source...")
    print("This will take 10-15 minutes...")

    # Check for .NET SDK
    try:
        result = subprocess.run(
            ["dotnet", "--version"],
            capture_output=True,
            text=True,
            check=True,
        )
        print(f"Using .NET SDK: {result.stdout.strip()}")
    except (subprocess.CalledProcessError, FileNotFoundError):
        raise RuntimeError(
            ".NET SDK not found. Please install .NET 8 SDK or later from https://dot.net"
        )

    # Get build script from GitHub
    temp_dir = Path(tempfile.gettempdir())
    script_url = f"https://raw.githubusercontent.com/{GITHUB_REPO}/main/scripts/build-roslyn-vbnet.sh"
    script_path = temp_dir / "build-roslyn-vbnet.sh"

    print("Downloading build script...")
    response = requests.get(script_url)
    response.raise_for_status()

    script_path.write_text(response.text)
    script_path.chmod(0o755)

    # Run build
    rid = get_platform_rid()
    print(f"Building for {rid}...")

    result = subprocess.run(
        [str(script_path), rid],
        cwd=str(temp_dir),
        check=True,
    )

    # Copy output
    source_dir = temp_dir / "roslyn-build" / "output"
    if not source_dir.exists():
        raise RuntimeError("Build failed - output directory not found")

    LS_DIR.mkdir(parents=True, exist_ok=True)

    print(f"Copying to {LS_DIR}...")
    for item in source_dir.iterdir():
        if item.is_file():
            shutil.copy2(item, LS_DIR / item.name)
        elif item.is_dir():
            shutil.copytree(item, LS_DIR / item.name, dirs_exist_ok=True)

    print("[OK] Build complete")


def install_language_server(force: bool = False, from_source: bool = False):
    """Install VB.NET language server"""
    if LS_DIR.exists() and not force:
        print(f"Language server already installed at {LS_DIR}")
        print("Use --force to reinstall")
        return

    if LS_DIR.exists():
        print("Removing existing installation...")
        shutil.rmtree(LS_DIR)

    if from_source:
        build_from_source()
    else:
        try:
            archive_path = download_prebuilt_binary()
            extract_binary(archive_path, LS_DIR)
            archive_path.unlink()  # Clean up
        except Exception as e:
            print(f"[WARN] Failed to download pre-built binary: {e}")
            print("Falling back to build from source...")
            build_from_source()

    # Verify installation
    issues = verify_installation()
    if issues:
        raise RuntimeError(f"Installation incomplete: {', '.join(issues)}")


def find_serena_installation() -> Path:
    """Find Serena's solidlsp package installation"""
    try:
        import solidlsp
        return Path(solidlsp.__file__).parent
    except ImportError:
        raise RuntimeError(
            "Serena not found. Please install: pip install serena-agent"
        )


def find_serena_config_path() -> Path:
    """Find Serena's config module where Language enum is defined"""
    try:
        import serena.config.serena_config as serena_config
        return Path(serena_config.__file__)
    except ImportError:
        # Try alternative import
        try:
            import serena
            serena_path = Path(serena.__file__).parent
            config_path = serena_path / "config" / "serena_config.py"
            if config_path.exists():
                return config_path
        except ImportError:
            pass
        raise RuntimeError(
            "Serena config not found. Please install: pip install serena-agent"
        )


def patch_serena():
    """Add VB.NET support to Serena"""
    solidlsp_path = find_serena_installation()
    ls_config_path = solidlsp_path / "ls_config.py"

    if not ls_config_path.exists():
        raise RuntimeError(f"ls_config.py not found at {ls_config_path}")

    # Find serena config path for Language enum
    try:
        serena_config_path = find_serena_config_path()
    except RuntimeError:
        serena_config_path = None
        print("[WARN] Could not find serena_config.py - Language enum may not be patched")

    # Copy vbnet_language_server.py into Serena's solidlsp/language_servers directory
    language_servers_path = solidlsp_path / "language_servers"
    if not language_servers_path.exists():
        raise RuntimeError(f"Language servers directory not found: {language_servers_path}")

    # Copy our vbnet_language_server.py
    source_file = Path(__file__).parent / "vbnet_language_server.py"
    dest_file = language_servers_path / "vbnet_language_server.py"

    if not dest_file.exists() or True:  # Always copy to ensure latest version
        print(f"Copying VB.NET language server to {dest_file}...")
        shutil.copy(source_file, dest_file)
        print("[OK] VB.NET language server file installed")

    # Patch serena_config.py first (for Language enum validation)
    if serena_config_path and serena_config_path.exists():
        _patch_serena_config(serena_config_path)

    # Then patch ls_config.py (for language server class)
    _patch_ls_config(ls_config_path)


def _patch_serena_config(config_path: Path):
    """Patch serena/config/serena_config.py to add VBNET to Language enum"""
    content = config_path.read_text()

    # Check if already patched (case-insensitive)
    if "vbnet" in content.lower():
        print("serena_config.py already has VB.NET support")
        return

    print(f"Patching {config_path}...")

    # Find the Language enum and add vbnet
    # Handle both uppercase (FSHARP) and lowercase (fsharp) enum patterns
    lines = content.split("\n")
    patched = False

    # Patterns to look for (both cases)
    patterns_to_match = [
        ('fsharp = "fsharp"', 'vbnet = "vbnet"'),
        ("fsharp = 'fsharp'", "vbnet = 'vbnet'"),
        ('FSHARP = "fsharp"', 'VBNET = "vbnet"'),
        ("FSHARP = 'fsharp'", "VBNET = 'vbnet'"),
        ('zig = "zig"', 'vbnet = "vbnet"'),
        ("zig = 'zig'", "vbnet = 'vbnet'"),
        ('ZIG = "zig"', 'VBNET = "vbnet"'),
        ("ZIG = 'zig'", "VBNET = 'vbnet'"),
        ('scala = "scala"', 'vbnet = "vbnet"'),
        ("scala = 'scala'", "vbnet = 'vbnet'"),
        ('julia = "julia"', 'vbnet = "vbnet"'),
        ("julia = 'julia'", "vbnet = 'vbnet'"),
    ]

    for i, line in enumerate(lines):
        line_lower = line.lower()
        for pattern, vbnet_line in patterns_to_match:
            if pattern in line_lower:
                # Match found - get indent from original line
                indent = len(line) - len(line.lstrip())
                lines.insert(i + 1, " " * indent + vbnet_line)
                patched = True
                break
        if patched:
            break

    if patched:
        config_path.write_text("\n".join(lines))
        print("[OK] serena_config.py patched with vbnet language")
    else:
        # Fallback: try to find any language enum entry pattern
        print("[WARN] Standard patterns not found, trying fallback...")
        for i, line in enumerate(lines):
            # Look for pattern like: word = "word" where both are the same
            stripped = line.strip()
            if '= "' in stripped and stripped.count('"') == 2:
                # Extract the value
                try:
                    parts = stripped.split('= "')
                    if len(parts) == 2:
                        name = parts[0].strip()
                        value = parts[1].rstrip('"')
                        if name.lower() == value.lower() and name.isalpha():
                            # This looks like a language enum entry
                            indent = len(line) - len(line.lstrip())
                            lines.insert(i + 1, " " * indent + 'vbnet = "vbnet"')
                            config_path.write_text("\n".join(lines))
                            print(f"[OK] serena_config.py patched (inserted after {name})")
                            return
                except:
                    pass
        print("[ERROR] Could not find location to patch Language enum in serena_config.py")


def _patch_ls_config(ls_config_path: Path):
    """Patch solidlsp/ls_config.py for language server configuration"""
    content = ls_config_path.read_text()

    # Check if already patched (case-insensitive)
    if "vbnet" in content.lower():
        print("ls_config.py already configured for VB.NET")
        return

    print(f"Patching {ls_config_path}...")

    # Add VBNET enum entry if Language enum exists here
    if "class Language(str, Enum):" in content:
        lines = content.split("\n")
        for i, line in enumerate(lines):
            if "class Language(str, Enum):" in line:
                for j in range(i, len(lines)):
                    line_lower = lines[j].lower()
                    if ("fsharp" in line_lower or "zig" in line_lower) and "=" in lines[j]:
                        indent = len(lines[j]) - len(lines[j].lstrip())
                        lines.insert(j + 1, " " * indent + 'VBNET = "vbnet"')
                        break
                break
        content = "\n".join(lines)

    # Add file matcher (handle both uppercase and lowercase)
    content_lower = content.lower()
    if "case self.fsharp:" in content_lower or "case self.FSHARP:" in content:
        lines = content.split("\n")
        for i, line in enumerate(lines):
            if "case self.fsharp:" in line.lower():
                for j in range(i, len(lines)):
                    if "return FilenameMatcher" in lines[j]:
                        indent = len(lines[j]) - len(lines[j].lstrip())
                        # Match case style of existing code
                        if "self.FSHARP" in lines[i]:
                            case_line = " " * (indent - 4) + "case self.VBNET:"
                        else:
                            case_line = " " * (indent - 4) + "case self.vbnet:"
                        lines.insert(j + 1, case_line)
                        lines.insert(j + 2, " " * indent + 'return FilenameMatcher("*.vb")')
                        break
                break
        content = "\n".join(lines)

    # Add language server import and case
    if 'from solidlsp.language_servers.fsharp_language_server import FSharpLanguageServer' in content:
        lines = content.split("\n")

        # Add import
        for i, line in enumerate(lines):
            if "from solidlsp.language_servers.fsharp_language_server" in line:
                lines.insert(
                    i + 1,
                    "from solidlsp.language_servers.vbnet_language_server import VBNetLanguageServer",
                )
                break

        content = "\n".join(lines)

        # Add case statement
        lines = content.split("\n")
        for i, line in enumerate(lines):
            if 'return FSharpLanguageServer' in line:
                if i > 0 and 'case self.FSHARP:' in lines[i-1]:
                    lines.insert(i + 1, "            case self.VBNET:")
                    lines.insert(i + 2, "                return VBNetLanguageServer")
                    break
        content = "\n".join(lines)

    ls_config_path.write_text(content)
    print("[OK] ls_config.py patched successfully")


def unpatch_serena():
    """Remove VB.NET support from Serena"""
    # Unpatch ls_config.py
    serena_path = find_serena_installation()
    ls_config_path = serena_path / "ls_config.py"

    if ls_config_path.exists():
        content = ls_config_path.read_text()
        if "VBNET" in content or "VBNetLanguageServer" in content:
            lines = content.split("\n")
            filtered_lines = [
                line
                for line in lines
                if "VBNET" not in line and "VBNetLanguageServer" not in line and "vbnet_language_server" not in line
            ]
            ls_config_path.write_text("\n".join(filtered_lines))
            print("[OK] ls_config.py unpatched")

    # Unpatch serena_config.py
    try:
        serena_config_path = find_serena_config_path()
        if serena_config_path.exists():
            content = serena_config_path.read_text()
            if "VBNET" in content or '"vbnet"' in content:
                lines = content.split("\n")
                filtered_lines = [
                    line
                    for line in lines
                    if "VBNET" not in line and '"vbnet"' not in line
                ]
                serena_config_path.write_text("\n".join(filtered_lines))
                print("[OK] serena_config.py unpatched")
    except RuntimeError:
        pass  # serena_config.py not found, skip

    print("[OK] Serena unpatched")


def verify_installation() -> List[str]:
    """Verify installation and return list of issues"""
    issues = []

    # Check language server directory
    if not LS_DIR.exists():
        issues.append("Language server directory not found")
        return issues

    # Check main DLL
    main_dll = LS_DIR / "Microsoft.CodeAnalysis.LanguageServer.dll"
    if not main_dll.exists():
        issues.append("Main language server DLL not found")

    # Check VB.NET assemblies
    vb_dlls = [
        "Microsoft.CodeAnalysis.VisualBasic.dll",
        "Microsoft.CodeAnalysis.VisualBasic.Features.dll",
        "Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll",
    ]

    for dll in vb_dlls:
        dll_path = LS_DIR / dll
        if not dll_path.exists():
            issues.append(f"Missing VB.NET assembly: {dll}")

    # Check Serena ls_config.py patch (case-insensitive)
    try:
        serena_path = find_serena_installation()
        ls_config = (serena_path / "ls_config.py").read_text().lower()
        if "vbnet" not in ls_config:
            issues.append("solidlsp/ls_config.py not patched with VB.NET support")
    except Exception as e:
        issues.append(f"Cannot verify ls_config.py patch: {e}")

    # Check serena_config.py patch (Language enum, case-insensitive)
    try:
        serena_config_path = find_serena_config_path()
        serena_config = serena_config_path.read_text().lower()
        if "vbnet" not in serena_config:
            issues.append("serena/config/serena_config.py not patched - Language enum missing vbnet")
    except Exception as e:
        issues.append(f"Cannot verify serena_config.py patch: {e}")

    return issues


def update_language_server():
    """Update language server to latest version"""
    print("Checking for updates...")
    # For now, just reinstall
    install_language_server(force=True)


def uninstall_language_server():
    """Remove language server"""
    if not LS_DIR.exists():
        print("Language server not installed")
        return

    shutil.rmtree(LS_DIR)
    print(f"Removed {LS_DIR}")


def get_installation_info() -> Dict[str, Any]:
    """Get installation information"""
    info = {
        "plugin_version": __version__,
        "ls_path": str(LS_DIR),
        "ls_status": "Installed" if LS_DIR.exists() else "Not installed",
        "serena_patched": False,
        "ls_assemblies": [],
    }

    # Check for assemblies
    if LS_DIR.exists():
        vb_dlls = [
            "Microsoft.CodeAnalysis.VisualBasic.dll",
            "Microsoft.CodeAnalysis.VisualBasic.Features.dll",
            "Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll",
        ]
        info["ls_assemblies"] = [dll for dll in vb_dlls if (LS_DIR / dll).exists()]

    # Check Serena patch (case-insensitive)
    try:
        serena_path = find_serena_installation()
        ls_config = (serena_path / "ls_config.py").read_text().lower()
        serena_config_path = find_serena_config_path()
        serena_config = serena_config_path.read_text().lower()
        info["serena_patched"] = "vbnet" in ls_config and "vbnet" in serena_config
    except:
        pass

    return info
