"""Dialog-based UI for managing SmartTools."""

import subprocess
import sys
import tempfile
from typing import Optional, Tuple, List

from .tool import (
    Tool, ToolArgument, PromptStep, CodeStep, Step,
    list_tools, load_tool, save_tool, delete_tool, tool_exists
)
from .providers import Provider, load_providers, add_provider, delete_provider, get_provider


def _check_urwid() -> bool:
    """Check if urwid is available (preferred - has mouse support)."""
    try:
        import urwid
        return True
    except ImportError:
        return False


def _check_snack() -> bool:
    """Check if snack (python3-newt) is available."""
    try:
        if '/usr/lib/python3/dist-packages' not in sys.path:
            sys.path.insert(0, '/usr/lib/python3/dist-packages')
        import snack
        return True
    except ImportError:
        return False


def check_dialog() -> str:
    """Check for available dialog program. Returns 'dialog', 'whiptail', or None."""
    for prog in ["dialog", "whiptail"]:
        try:
            subprocess.run([prog, "--version"], capture_output=True, check=False)
            return prog
        except FileNotFoundError:
            continue
    return None


def run_dialog(args: list[str], dialog_prog: str = "dialog") -> Tuple[int, str]:
    """Run a dialog command and return (exit_code, output)."""
    try:
        if dialog_prog == "whiptail":
            result = subprocess.run(
                [dialog_prog] + args,
                stderr=subprocess.PIPE,
                text=True
            )
            return result.returncode, result.stderr.strip()
        else:
            cmd_with_stdout = [dialog_prog, "--stdout"] + args
            result = subprocess.run(
                cmd_with_stdout,
                stdout=subprocess.PIPE,
                text=True
            )
            return result.returncode, result.stdout.strip()
    except Exception as e:
        return 1, ""


def show_menu(title: str, choices: list[tuple[str, str]], dialog_prog: str) -> Optional[str]:
    """Show a menu and return the selected item."""
    args = ["--title", title, "--menu", "Choose an option:", "20", "75", str(len(choices))]
    for tag, desc in choices:
        args.extend([tag, desc])

    code, output = run_dialog(args, dialog_prog)
    return output if code == 0 else None


def show_input(title: str, prompt: str, initial: str = "", dialog_prog: str = "dialog") -> Optional[str]:
    """Show an input box and return the entered text."""
    args = ["--title", title, "--inputbox", prompt, "10", "60", initial]
    code, output = run_dialog(args, dialog_prog)
    return output if code == 0 else None


def show_textbox(title: str, text: str, dialog_prog: str = "dialog") -> Optional[str]:
    """Show a text editor for multi-line input."""
    with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
        f.write(text)
        temp_path = f.name

    try:
        args = ["--title", title, "--editbox", temp_path, "20", "75"]
        code, output = run_dialog(args, dialog_prog)
        return output if code == 0 else None
    finally:
        import os
        os.unlink(temp_path)


def show_yesno(title: str, text: str, dialog_prog: str = "dialog") -> bool:
    """Show a yes/no dialog."""
    args = ["--title", title, "--yesno", text, "10", "60"]
    code, _ = run_dialog(args, dialog_prog)
    return code == 0


def show_message(title: str, text: str, dialog_prog: str = "dialog"):
    """Show a message box."""
    args = ["--title", title, "--msgbox", text, "15", "70"]
    run_dialog(args, dialog_prog)


def show_mixed_form(title: str, fields: dict, dialog_prog: str, height: int = 20) -> Optional[dict]:
    """Show a form with multiple fields using --mixedform."""
    args = ["--title", title, "--mixedform",
            "Tab: next field | Enter: submit | Esc: cancel",
            str(height), "75", "0"]

    field_names = list(fields.keys())
    y = 1
    for name in field_names:
        label, initial, field_type = fields[name]
        args.extend([
            label, str(y), "1",
            initial, str(y), "18",
            "52", "256", str(field_type)
        ])
        y += 1

    code, output = run_dialog(args, dialog_prog)

    if code != 0:
        return None

    values = output.split('\n')
    result = {}
    for i, name in enumerate(field_names):
        result[name] = values[i] if i < len(values) else ""

    return result


# ============ Provider Management ============

def select_provider(dialog_prog: str) -> Optional[str]:
    """Show provider selection menu with option to create new."""
    providers = load_providers()

    choices = [(p.name, f"{p.description} ({p.command})") for p in providers]
    choices.append(("__new__", "[ + Add New Provider ]"))

    selected = show_menu("Select Provider", choices, dialog_prog)

    if selected == "__new__":
        provider = create_provider_form(dialog_prog)
        if provider:
            add_provider(provider)
            return provider.name
        return None

    return selected


def create_provider_form(dialog_prog: str, existing: Optional[Provider] = None) -> Optional[Provider]:
    """Show form for creating/editing a provider."""
    title = f"Edit Provider: {existing.name}" if existing else "Add New Provider"

    fields = {
        "name": (
            "Name:",
            existing.name if existing else "",
            2 if existing else 0  # readonly if editing
        ),
        "command": (
            "Command:",
            existing.command if existing else "",
            0
        ),
        "description": (
            "Description:",
            existing.description if existing else "",
            0
        ),
    }

    result = show_mixed_form(title, fields, dialog_prog, height=12)

    if not result:
        return None

    name = result["name"].strip()
    command = result["command"].strip()

    if not name:
        show_message("Error", "Provider name is required.", dialog_prog)
        return None

    if not command:
        show_message("Error", "Command is required.", dialog_prog)
        return None

    return Provider(
        name=name,
        command=command,
        description=result["description"].strip()
    )


def ui_manage_providers(dialog_prog: str):
    """Manage providers menu."""
    while True:
        providers = load_providers()
        choices = [(p.name, f"{p.command}") for p in providers]
        choices.append(("__add__", "[ + Add New Provider ]"))
        choices.append(("__back__", "[ <- Back to Main Menu ]"))

        selected = show_menu("Manage Providers", choices, dialog_prog)

        if selected is None or selected == "__back__":
            break
        elif selected == "__add__":
            provider = create_provider_form(dialog_prog)
            if provider:
                add_provider(provider)
                show_message("Success", f"Provider '{provider.name}' added.", dialog_prog)
        else:
            # Edit or delete existing provider
            provider = get_provider(selected)
            if provider:
                action = show_menu(
                    f"Provider: {selected}",
                    [
                        ("edit", "Edit provider"),
                        ("delete", "Delete provider"),
                        ("back", "Back"),
                    ],
                    dialog_prog
                )

                if action == "edit":
                    updated = create_provider_form(dialog_prog, provider)
                    if updated:
                        add_provider(updated)
                        show_message("Success", f"Provider '{updated.name}' updated.", dialog_prog)
                elif action == "delete":
                    if show_yesno("Confirm", f"Delete provider '{selected}'?", dialog_prog):
                        delete_provider(selected)
                        show_message("Deleted", f"Provider '{selected}' deleted.", dialog_prog)


# ============ Tool Builder UI ============

def format_tool_summary(tool: Tool) -> str:
    """Format a summary of the tool's components."""
    lines = []
    lines.append(f"Name: {tool.name}")
    lines.append(f"Description: {tool.description or '(none)'}")
    lines.append("")

    if tool.arguments:
        lines.append("Arguments:")
        for arg in tool.arguments:
            default = f" = {arg.default}" if arg.default else ""
            lines.append(f"  {arg.flag} -> {{{arg.variable}}}{default}")
        lines.append("")

    if tool.steps:
        lines.append("Steps:")
        for i, step in enumerate(tool.steps):
            if isinstance(step, PromptStep):
                preview = step.prompt[:40].replace('\n', ' ') + "..."
                lines.append(f"  {i+1}. PROMPT [{step.provider}] -> {{{step.output_var}}}")
                lines.append(f"      {preview}")
            elif isinstance(step, CodeStep):
                preview = step.code[:40].replace('\n', ' ') + "..."
                lines.append(f"  {i+1}. CODE -> {{{step.output_var}}}")
                lines.append(f"      {preview}")
        lines.append("")

    lines.append(f"Output: {tool.output}")

    return "\n".join(lines)


def get_available_variables(tool: Tool, up_to_step: int = -1) -> List[str]:
    """Get list of available variables at a given point in the tool."""
    variables = ["input"]

    for arg in tool.arguments:
        variables.append(arg.variable)

    if up_to_step == -1:
        up_to_step = len(tool.steps)

    for i, step in enumerate(tool.steps):
        if i >= up_to_step:
            break
        variables.append(step.output_var)

    return variables


def edit_argument(dialog_prog: str, existing: Optional[ToolArgument] = None) -> Optional[ToolArgument]:
    """Edit or create an argument."""
    title = f"Edit Argument: {existing.flag}" if existing else "Add Argument"

    fields = {
        "flag": ("Flag:", existing.flag if existing else "--", 0),
        "variable": ("Variable:", existing.variable if existing else "", 0),
        "default": ("Default:", existing.default or "" if existing else "", 0),
        "description": ("Description:", existing.description if existing else "", 0),
    }

    result = show_mixed_form(title, fields, dialog_prog, height=14)
    if not result:
        return None

    flag = result["flag"].strip()
    variable = result["variable"].strip()

    if not flag:
        show_message("Error", "Flag is required (e.g., --max-size).", dialog_prog)
        return None

    if not variable:
        # Auto-generate variable name from flag
        variable = flag.lstrip("-").replace("-", "_")

    return ToolArgument(
        flag=flag,
        variable=variable,
        default=result["default"].strip() or None,
        description=result["description"].strip()
    )


def edit_prompt_step(dialog_prog: str, existing: Optional[PromptStep] = None,
                     available_vars: List[str] = None) -> Optional[PromptStep]:
    """Edit or create a prompt step."""
    title = "Edit Prompt Step" if existing else "Add Prompt Step"

    # First, select provider
    provider = select_provider(dialog_prog)
    if not provider:
        provider = existing.provider if existing else "mock"

    # Show variable help
    var_help = "Available: " + ", ".join(f"{{{v}}}" for v in (available_vars or ["input"]))

    # Edit prompt text
    default_prompt = existing.prompt if existing else f"Process this input:\n\n{{input}}"
    prompt = show_textbox(f"Prompt Template\n{var_help}", default_prompt, dialog_prog)
    if prompt is None:
        return None

    # Get output variable
    output_var = show_input(
        "Output Variable",
        "Variable name to store the result:",
        existing.output_var if existing else "result",
        dialog_prog
    )
    if not output_var:
        return None

    return PromptStep(
        prompt=prompt,
        provider=provider,
        output_var=output_var.strip()
    )


def edit_code_step(dialog_prog: str, existing: Optional[CodeStep] = None,
                   available_vars: List[str] = None) -> Optional[CodeStep]:
    """Edit or create a code step."""
    title = "Edit Code Step" if existing else "Add Code Step"

    # Show variable help
    var_help = "Variables: " + ", ".join(available_vars or ["input"])
    var_help += "\nSet 'result' variable for output"

    # Edit code
    default_code = existing.code if existing else "# Available variables: " + ", ".join(available_vars or ["input"]) + "\n# Set 'result' for output\nresult = input.upper()"
    code = show_textbox(f"Python Code\n{var_help}", default_code, dialog_prog)
    if code is None:
        return None

    # Get output variable
    output_var = show_input(
        "Output Variable",
        "Variable name to store the result:",
        existing.output_var if existing else "processed",
        dialog_prog
    )
    if not output_var:
        return None

    return CodeStep(
        code=code,
        output_var=output_var.strip()
    )


def edit_tool_info(tool: Tool, is_edit: bool, dialog_prog: str) -> None:
    """Edit basic tool info (name, description, output)."""
    while True:
        # Build info section menu
        output_preview = tool.output[:35] + "..." if len(tool.output) > 35 else tool.output
        args_count = len(tool.arguments)
        args_summary = f"({args_count} defined)" if args_count else "(none)"

        choices = [
            ("name", f"Name: {tool.name or '(not set)'}"),
            ("desc", f"Description: {tool.description[:35] + '...' if len(tool.description) > 35 else tool.description or '(none)'}"),
            ("---1", "─" * 40),
        ]

        # Show arguments
        if tool.arguments:
            for i, arg in enumerate(tool.arguments):
                default = f" = {arg.default}" if arg.default else ""
                choices.append((f"arg_{i}", f"  {arg.flag} -> {{{arg.variable}}}{default}"))
        choices.append(("add_arg", "  [ + Add Argument ]"))

        choices.append(("---2", "─" * 40))
        choices.append(("output", f"Output Template: {output_preview}"))
        choices.append(("---3", "─" * 40))
        choices.append(("back", "<- Back to Tool Builder"))

        selected = show_menu("Tool Info & Arguments", choices, dialog_prog)

        if selected is None or selected == "back":
            break

        elif selected == "name":
            if is_edit:
                show_message("Info", "Cannot change tool name after creation.", dialog_prog)
            else:
                new_name = show_input("Tool Name", "Enter tool name:", tool.name, dialog_prog)
                if new_name:
                    tool.name = new_name.strip()

        elif selected == "desc":
            new_desc = show_input("Description", "Enter tool description:", tool.description, dialog_prog)
            if new_desc is not None:
                tool.description = new_desc.strip()

        elif selected == "add_arg":
            arg = edit_argument(dialog_prog)
            if arg:
                tool.arguments.append(arg)

        elif selected.startswith("arg_"):
            idx = int(selected[4:])
            arg = tool.arguments[idx]
            action = show_menu(
                f"Argument: {arg.flag}",
                [("edit", "Edit"), ("delete", "Delete"), ("back", "Back")],
                dialog_prog
            )
            if action == "edit":
                updated = edit_argument(dialog_prog, arg)
                if updated:
                    tool.arguments[idx] = updated
            elif action == "delete":
                if show_yesno("Delete", f"Delete argument {arg.flag}?", dialog_prog):
                    tool.arguments.pop(idx)

        elif selected == "output":
            available = get_available_variables(tool)
            var_help = "Variables: " + ", ".join(f"{{{v}}}" for v in available)
            new_output = show_textbox(f"Output Template\n{var_help}", tool.output, dialog_prog)
            if new_output is not None:
                tool.output = new_output


def edit_tool_steps(tool: Tool, dialog_prog: str) -> None:
    """Edit tool processing steps."""
    while True:
        # Build steps section menu
        choices = []

        if tool.steps:
            for i, step in enumerate(tool.steps):
                if isinstance(step, PromptStep):
                    choices.append((f"step_{i}", f"{i+1}. PROMPT [{step.provider}] -> {{{step.output_var}}}"))
                elif isinstance(step, CodeStep):
                    choices.append((f"step_{i}", f"{i+1}. CODE -> {{{step.output_var}}}"))
        else:
            choices.append(("none", "(no steps defined)"))

        choices.append(("---1", "─" * 40))
        choices.append(("add_prompt", "[ + Add Prompt Step ]"))
        choices.append(("add_code", "[ + Add Code Step ]"))
        choices.append(("---2", "─" * 40))
        choices.append(("back", "<- Back to Tool Builder"))

        selected = show_menu("Processing Steps", choices, dialog_prog)

        if selected is None or selected == "back" or selected == "none":
            if selected == "none":
                continue
            break

        elif selected == "add_prompt":
            available = get_available_variables(tool)
            step = edit_prompt_step(dialog_prog, available_vars=available)
            if step:
                tool.steps.append(step)

        elif selected == "add_code":
            available = get_available_variables(tool)
            step = edit_code_step(dialog_prog, available_vars=available)
            if step:
                tool.steps.append(step)

        elif selected.startswith("step_"):
            idx = int(selected[5:])
            step = tool.steps[idx]
            step_type = "Prompt" if isinstance(step, PromptStep) else "Code"

            move_choices = [("edit", "Edit"), ("delete", "Delete")]
            if idx > 0:
                move_choices.insert(1, ("move_up", "Move Up"))
            if idx < len(tool.steps) - 1:
                move_choices.insert(2 if idx > 0 else 1, ("move_down", "Move Down"))
            move_choices.append(("back", "Back"))

            action = show_menu(f"Step {idx+1}: {step_type}", move_choices, dialog_prog)

            if action == "edit":
                available = get_available_variables(tool, idx)
                if isinstance(step, PromptStep):
                    updated = edit_prompt_step(dialog_prog, step, available)
                else:
                    updated = edit_code_step(dialog_prog, step, available)
                if updated:
                    tool.steps[idx] = updated
            elif action == "move_up" and idx > 0:
                tool.steps[idx], tool.steps[idx-1] = tool.steps[idx-1], tool.steps[idx]
            elif action == "move_down" and idx < len(tool.steps) - 1:
                tool.steps[idx], tool.steps[idx+1] = tool.steps[idx+1], tool.steps[idx]
            elif action == "delete":
                if show_yesno("Delete", f"Delete step {idx+1}?", dialog_prog):
                    tool.steps.pop(idx)


def tool_builder(dialog_prog: str, existing: Optional[Tool] = None) -> Optional[Tool]:
    """Main tool builder interface with tabbed sections."""
    is_edit = existing is not None

    # Initialize tool
    if existing:
        tool = Tool(
            name=existing.name,
            description=existing.description,
            arguments=list(existing.arguments),
            steps=list(existing.steps),
            output=existing.output
        )
    else:
        tool = Tool(name="", description="", arguments=[], steps=[], output="{input}")

    while True:
        # Build main menu with section summaries
        args_count = len(tool.arguments)
        steps_count = len(tool.steps)

        # Info summary
        name_display = tool.name or "(not set)"
        info_summary = f"{name_display}"
        if tool.arguments:
            info_summary += f" | {args_count} arg{'s' if args_count != 1 else ''}"

        # Steps summary
        if tool.steps:
            step_types = []
            for s in tool.steps:
                if isinstance(s, PromptStep):
                    step_types.append(f"P:{s.provider}")
                else:
                    step_types.append("C")
            steps_summary = " -> ".join(step_types)
        else:
            steps_summary = "(none)"

        choices = [
            ("info", f"[1] Info & Args    : {info_summary}"),
            ("steps", f"[2] Steps          : {steps_summary}"),
            ("---", "─" * 50),
            ("preview", "Preview Full Summary"),
            ("save", "Save Tool"),
            ("cancel", "Cancel"),
        ]

        title = f"Tool Builder: {tool.name}" if tool.name else "Tool Builder: New Tool"
        selected = show_menu(title, choices, dialog_prog)

        if selected is None or selected == "cancel":
            if show_yesno("Cancel", "Discard changes?", dialog_prog):
                return None
            continue

        elif selected == "info":
            edit_tool_info(tool, is_edit, dialog_prog)

        elif selected == "steps":
            edit_tool_steps(tool, dialog_prog)

        elif selected == "preview":
            summary = format_tool_summary(tool)
            show_message("Tool Summary", summary, dialog_prog)

        elif selected == "save":
            if not tool.name:
                show_message("Error", "Tool name is required. Go to Info & Args to set it.", dialog_prog)
                continue

            if not is_edit and tool_exists(tool.name):
                if not show_yesno("Overwrite?", f"Tool '{tool.name}' exists. Overwrite?", dialog_prog):
                    continue

            return tool


# ============ Main Menu Functions ============

def ui_list_tools(dialog_prog: str):
    """Show list of tools."""
    tools = list_tools()
    if not tools:
        show_message("Tools", "No tools found.\n\nCreate your first tool from the main menu.", dialog_prog)
        return

    text = "Available tools:\n\n"
    for name in tools:
        tool = load_tool(name)
        if tool:
            text += f"  {name}: {tool.description or 'No description'}\n"
            if tool.arguments:
                args = ", ".join(arg.flag for arg in tool.arguments)
                text += f"    Arguments: {args}\n"
            if tool.steps:
                step_info = []
                for step in tool.steps:
                    if isinstance(step, PromptStep):
                        step_info.append(f"PROMPT[{step.provider}]")
                    else:
                        step_info.append("CODE")
                text += f"    Steps: {' -> '.join(step_info)}\n"
            text += "\n"

    show_message("Tools", text, dialog_prog)


def ui_create_tool(dialog_prog: str):
    """Create a new tool."""
    tool = tool_builder(dialog_prog)
    if tool:
        path = save_tool(tool)

        # Build usage example
        usage = f"{tool.name}"
        for arg in tool.arguments:
            if arg.default:
                usage += f" [{arg.flag} <{arg.variable}>]"
            else:
                usage += f" {arg.flag} <{arg.variable}>"
        usage += " < input.txt"

        show_message("Success",
                     f"Tool '{tool.name}' created!\n\n"
                     f"Config: {path}\n\n"
                     f"Usage: {usage}",
                     dialog_prog)


def ui_edit_tool(dialog_prog: str):
    """Edit an existing tool."""
    tools = list_tools()
    if not tools:
        show_message("Edit Tool", "No tools found.", dialog_prog)
        return

    choices = []
    for name in tools:
        tool = load_tool(name)
        desc = tool.description if tool else "No description"
        choices.append((name, desc))

    selected = show_menu("Select Tool to Edit", choices, dialog_prog)

    if selected:
        existing = load_tool(selected)
        if existing:
            tool = tool_builder(dialog_prog, existing)
            if tool:
                save_tool(tool)
                show_message("Success", f"Tool '{tool.name}' updated!", dialog_prog)


def ui_delete_tool(dialog_prog: str):
    """Delete a tool."""
    tools = list_tools()
    if not tools:
        show_message("Delete Tool", "No tools found.", dialog_prog)
        return

    choices = []
    for name in tools:
        tool = load_tool(name)
        desc = tool.description if tool else "No description"
        choices.append((name, desc))

    selected = show_menu("Select Tool to Delete", choices, dialog_prog)

    if selected:
        if show_yesno("Confirm Delete", f"Delete tool '{selected}'?\n\nThis cannot be undone.", dialog_prog):
            if delete_tool(selected):
                show_message("Deleted", f"Tool '{selected}' deleted.", dialog_prog)
            else:
                show_message("Error", f"Failed to delete '{selected}'.", dialog_prog)


def ui_test_tool(dialog_prog: str):
    """Test a tool with mock provider."""
    tools = list_tools()
    if not tools:
        show_message("Test Tool", "No tools found.", dialog_prog)
        return

    choices = []
    for name in tools:
        tool = load_tool(name)
        desc = tool.description if tool else "No description"
        choices.append((name, desc))

    selected = show_menu("Select Tool to Test", choices, dialog_prog)

    if selected:
        tool = load_tool(selected)
        if tool:
            test_input = show_textbox("Test Input", "Enter test input here...", dialog_prog)
            if test_input:
                from .runner import run_tool
                output, code = run_tool(
                    tool=tool,
                    input_text=test_input,
                    custom_args={},
                    provider_override="mock",
                    dry_run=False,
                    show_prompt=False,
                    verbose=False
                )
                result_text = f"Exit code: {code}\n\n--- Output ---\n{output[:1000]}"
                if len(output) > 1000:
                    result_text += "\n... (truncated)"
                show_message("Test Result", result_text, dialog_prog)


def main_menu(dialog_prog: str):
    """Show the main menu."""
    while True:
        choice = show_menu(
            "SmartTools Manager",
            [
                ("list", "List all tools"),
                ("create", "Create new tool"),
                ("edit", "Edit existing tool"),
                ("delete", "Delete tool"),
                ("test", "Test tool (mock provider)"),
                ("providers", "Manage providers"),
                ("exit", "Exit"),
            ],
            dialog_prog
        )

        if choice is None or choice == "exit":
            break
        elif choice == "list":
            ui_list_tools(dialog_prog)
        elif choice == "create":
            ui_create_tool(dialog_prog)
        elif choice == "edit":
            ui_edit_tool(dialog_prog)
        elif choice == "delete":
            ui_delete_tool(dialog_prog)
        elif choice == "test":
            ui_test_tool(dialog_prog)
        elif choice == "providers":
            ui_manage_providers(dialog_prog)


def run_ui():
    """Entry point for the UI."""
    # Prefer urwid (has mouse support)
    if _check_urwid():
        from .ui_urwid import run_ui as run_urwid_ui
        run_urwid_ui()
        return

    # Fallback to snack (BIOS-style)
    if _check_snack():
        from .ui_snack import run_ui as run_snack_ui
        run_snack_ui()
        return

    # Fallback to dialog/whiptail
    dialog_prog = check_dialog()

    if not dialog_prog:
        print("Error: No TUI library found.", file=sys.stderr)
        print("Install one of:", file=sys.stderr)
        print("  pip install urwid          (recommended - has mouse support)", file=sys.stderr)
        print("  sudo apt install python3-newt", file=sys.stderr)
        print("  sudo apt install dialog", file=sys.stderr)
        sys.exit(1)

    try:
        main_menu(dialog_prog)
    except KeyboardInterrupt:
        pass
    finally:
        subprocess.run(["clear"], check=False)


if __name__ == "__main__":
    run_ui()
