import os
import shutil
import difflib
import datetime

from rich.tree import Tree
from rich.panel import Panel
from rich.prompt import Confirm
from rich.syntax import Syntax

from nova_cli.local.ui import ui
import core.prompts as prompts  # keep as-is for now
from nova_cli.local.utils import check_syntax

from nova_cli.local.file_manager.path_ops import resolve_path, validate_path
from nova_cli.local.file_manager.git_ops import commit_changes


def create_backup(filepath: str) -> None:
    """Moves existing file to .nova/backups with timestamp."""
    try:
        if not os.path.exists(filepath):
            return

        root = os.path.abspath(os.getcwd())
        backup_root = os.path.join(root, ".nova", "backups")

        rel_path = os.path.relpath(filepath, root)
        dest_base = os.path.join(backup_root, rel_path)
        dest_dir = os.path.dirname(dest_base)
        os.makedirs(dest_dir, exist_ok=True)

        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        dest_path = f"{dest_base}.{timestamp}.bak"

        shutil.copy2(filepath, dest_path)
        ui.print(f"[dim]>> Backup archived: .nova/backups/{rel_path}.{timestamp}.bak[/dim]")
    except Exception as e:
        ui.print(f"[yellow]>> Backup Warning: Could not create backup for {filepath}: {e}[/yellow]")


def map_directory(path: str = ".") -> None:
    """Visualizes file structure."""
    tree = Tree(f"[bold cyan]{os.path.basename(os.path.abspath(path))}[/bold cyan]")
    try:
        paths = sorted(
            os.listdir(path),
            key=lambda x: (not os.path.isdir(os.path.join(path, x)), x.lower()),
        )
        for entry in paths:
            if entry.startswith(".") or entry == "__pycache__":
                continue

            full_path = os.path.join(path, entry)
            if os.path.isdir(full_path):
                branch = tree.add(f"[bold yellow]📂 {entry}[/bold yellow]")
                try:
                    sub_paths = sorted(os.listdir(full_path))
                    for sub in sub_paths:
                        if sub.startswith(".") or sub == "__pycache__":
                            continue
                        sub_full = os.path.join(full_path, sub)
                        if os.path.isdir(sub_full):
                            branch.add(f"[bold yellow]📂 {sub}[/bold yellow]")
                        else:
                            branch.add(f"[dim]📄 {sub}[/dim]")
                except PermissionError:
                    branch.add("[red]ACCESS DENIED[/red]")
            else:
                tree.add(f"📄 {entry}")
    except Exception as e:
        tree.add(f"[red]Error: {e}[/red]")

    ui.print(Panel(tree, title="Directory Map", border_style="cyan"))


def get_project_files(extension: str = ".py") -> list[str]:
    """Scans the project for files with extension, excluding ignored dirs."""
    excluded = {
        ".git",
        "__pycache__",
        "venv",
        "env",
        "node_modules",
        ".idea",
        ".vscode",
        "dist",
        "build",
        ".next",
        ".ds_store",
        ".mypy_cache",
        ".nova",
    }

    source_files: list[str] = []
    start_path = os.getcwd()

    for root, dirs, files in os.walk(start_path):
        dirs[:] = [d for d in dirs if d not in excluded and not d.startswith(".")]

        for file in files:
            if file.endswith(extension):
                full_path = os.path.join(root, file)
                source_files.append(os.path.relpath(full_path, start_path))

    return sorted(source_files)


def run_creation_wizard() -> None:
    ui.print(Panel("[bold yellow]creation_wizard.exe initialized[/bold yellow]", border_style="yellow"))

    folder_name = ui.input("[cyan]1. Target Folder Name > [/cyan]").strip()
    if not folder_name:
        return

    files_input = ui.input("[cyan]2. File Names (space separated) > [/cyan]").strip()
    if not files_input:
        return
    file_names = files_input.split()

    extension = ui.input("[cyan]3. Extension (e.g. .py) > [/cyan]").strip()
    if not extension.startswith("."):
        extension = "." + extension

    try:
        target_dir = validate_path(os.path.join(os.getcwd(), folder_name))
        os.makedirs(target_dir, exist_ok=True)
        ui.print(f"[green]>> Folder created: {folder_name}[/green]")

        for name in file_names:
            fname = f"{name}{extension}"
            fpath = validate_path(os.path.join(target_dir, fname))

            if not os.path.exists(fpath):
                with open(fpath, "w", encoding="utf-8") as f:
                    f.write(f"# File: {fname}\n")
                ui.print(f"   [dim]Created: {fname}[/dim]")
            else:
                ui.print(f"   [yellow]Skipped: {fname}[/yellow]")

        prompts.clear_file_tree_cache()

    except Exception as e:
        ui.print(f"[bold red]>> ERROR: {e}[/bold red]")


def show_diff(filepath: str, new_content: str) -> None:
    """Displays diff between existing file and new content."""
    if not os.path.exists(filepath):
        ui.print("[dim]>> New File (No diff available)[/dim]")
        return

    with open(filepath, "r", encoding="utf-8") as f:
        old_lines = f.readlines()

    new_lines = new_content.splitlines(keepends=True)

    diff = list(
        difflib.unified_diff(
            old_lines,
            new_lines,
            fromfile=f"original/{os.path.basename(filepath)}",
            tofile=f"proposed/{os.path.basename(filepath)}",
        )
    )

    if not diff:
        ui.print("[dim]>> Content is identical.[/dim]")
        return

    diff_text = "".join(diff)
    syntax = Syntax(diff_text, "diff", theme="monokai", line_numbers=True)
    ui.print(Panel(syntax, title=f"Changes: {os.path.basename(filepath)}", border_style="yellow"))


def safe_delete(filepath: str) -> None:
    """Unified delete with security and confirmation."""
    try:
        full_path = validate_path(filepath)
        if not os.path.exists(full_path):
            ui.print(f"[yellow]>> Delete skipped: {filepath} not found.[/yellow]")
            return

        ui.print(Panel(f"Target: [bold red]{full_path}[/bold red]", title="DELETE CONFIRMATION", style="red"))
        if Confirm.ask(f">> DELETE {os.path.basename(full_path)} permanently?"):
            if os.path.isdir(full_path):
                shutil.rmtree(full_path)
            else:
                os.remove(full_path)

            ui.print(f"[bold red]>> DELETED: {os.path.basename(full_path)}[/bold red]")
            prompts.clear_file_tree_cache()
            commit_changes(f"Deleted {os.path.basename(full_path)}", full_path)
        else:
            ui.print("[dim]>> Delete cancelled.[/dim]")

    except Exception as e:
        ui.print(f"[bold red]>> DELETE ERROR: {e}[/bold red]")


def safe_write(filepath: str, content: str) -> bool:
    """
    Unified write enforcing:
    security, syntax checking, diff preview, confirmation, backup, semantic commit.
    """
    try:
        full_path = validate_path(resolve_path(filepath))
    except PermissionError as e:
        ui.print(f"[bold red]{e}[/bold red]")
        return False

    is_valid, syntax_err = check_syntax(content, full_path)
    if not is_valid:
        ui.print(f"[bold red]>> Warning: Code has Syntax Error: {syntax_err}[/bold red]")
        if not Confirm.ask(">> Force write invalid code?"):
            return False

    show_diff(full_path, content)

    ui.print(
        Panel(
            f"Target: [bold cyan]{full_path}[/bold cyan]\nBytes: {len(content)}",
            title="WRITE CONFIRMATION",
            style="yellow",
        )
    )

    prompt_msg = (
        f">> OVERWRITE {os.path.basename(full_path)}?"
        if os.path.exists(full_path)
        else f">> CREATE {os.path.basename(full_path)}?"
    )

    if not Confirm.ask(prompt_msg):
        ui.print("[dim]>> Cancelled.[/dim]")
        return False

    try:
        directory = os.path.dirname(full_path)
        if directory and not os.path.exists(directory):
            os.makedirs(directory, exist_ok=True)

        tmp_path = f"{full_path}.tmp"
        with open(tmp_path, "w", encoding="utf-8") as f:
            f.write(content)

        if os.path.exists(full_path):
            create_backup(full_path)

        os.replace(tmp_path, full_path)

        ui.print(f"[bold green]>> SUCCESS: {os.path.basename(full_path)} written.[/bold green]")
        commit_changes(f"Updated {os.path.basename(full_path)}", full_path, use_semantic=True)

        prompts.clear_file_tree_cache()
        return True

    except Exception as e:
        if "tmp_path" in locals() and os.path.exists(tmp_path):
            os.remove(tmp_path)
        ui.print(f"[bold red]>> WRITE ERROR: {e}[/bold red]")
        return False


def save_code_to_file(active_file: str | None, code_content: str | None) -> None:
    """Saves provided code content to active file."""
    if not active_file:
        ui.print("[red]>> ERROR: No active file loaded.[/red]")
        return

    if not code_content:
        ui.print("[red]>> ERROR: No stored code found to apply.[/red]")
        return

    safe_write(active_file, code_content)


def load_file(filepath: str) -> tuple[str | None, str]:
    try:
        full_path = validate_path(resolve_path(filepath))

        if not os.path.exists(full_path):
            ui.print(f"[red]>> NOT FOUND: {filepath}[/red]")
            return None, ""

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

        ui.print(f"[dim cyan]>> UPLOADING {len(content)} BYTES...[/dim cyan]")
        return full_path, content

    except PermissionError as e:
        ui.print(f"[bold red]{e}[/bold red]")
        return None, ""
    except Exception as e:
        ui.print(f"[red]>> READ ERROR: {e}[/red]")
        return None, ""
