"""
Code generator for transition graphs.

Generates Python code from TransitionGraph data structures.
All generation functions are pure - they take data and return strings.
"""
from __future__ import annotations

from datetime import datetime

from railway.core.dag.types import TransitionGraph


def generate_transition_code(graph: TransitionGraph, source_file: str) -> str:
    """
    Generate complete transition code file.

    This is the main entry point for code generation.
    Returns a complete, valid Python file as a string.

    Args:
        graph: Parsed transition graph
        source_file: Path to source YAML file

    Returns:
        Generated Python code as string

    Note:
        Call validate_graph(graph) before this to ensure valid output.
    """
    class_name = _to_class_name(graph.entrypoint)

    # Get start node function name
    start_node = graph.get_node(graph.start_node)
    start_function = (
        start_node.function if start_node else "None  # ERROR: start node not found"
    )

    parts = [
        _generate_header(source_file),
        _generate_framework_imports(),
        generate_imports(graph),
        "",
        generate_state_enum(graph),
        "",
        generate_exit_enum(graph),
        "",
        generate_transition_table(graph),
        "",
        generate_metadata(graph, source_file),
        "",
        _generate_helper_functions(class_name, start_function),
    ]

    return "\n".join(parts)


def _generate_header(source_file: str) -> str:
    """Generate file header with warning."""
    timestamp = datetime.now().isoformat()
    return f"""# DO NOT EDIT - Generated by `railway sync transition`
# Source: {source_file}
# Generated at: {timestamp}
#
# This file is auto-generated from the transition graph YAML.
# Any manual changes will be overwritten on next sync.
"""


def _generate_framework_imports() -> str:
    """Generate framework imports."""
    return """from typing import Callable
from railway.core.dag.state import NodeOutcome, ExitOutcome
from railway.core.dag.runner import Exit
"""


def generate_imports(graph: TransitionGraph) -> str:
    """
    Generate import statements for all nodes.

    Args:
        graph: Transition graph

    Returns:
        Import statements as string
    """
    lines = ["# Node imports"]
    for node in graph.nodes:
        lines.append(f"from {node.module} import {node.function}")
    return "\n".join(lines)


def generate_state_enum(graph: TransitionGraph) -> str:
    """
    Generate state enum from graph transitions.

    Args:
        graph: Transition graph

    Returns:
        State enum class definition as string
    """
    class_name = _to_class_name(graph.entrypoint)
    lines = [
        f"class {class_name}State(NodeOutcome):",
        '    """Auto-generated state enum for this workflow."""',
    ]

    # Collect unique states per node
    for node in graph.nodes:
        states = graph.get_states_for_node(node.name)
        if states:
            lines.append(f"    # {node.name}")
            for state in states:
                enum_name = _to_enum_name(node.name, state)
                full_state = f"{node.name}::{state}"
                lines.append(f'    {enum_name} = "{full_state}"')

    if len(lines) == 2:
        lines.append("    pass  # No states defined")

    return "\n".join(lines)


def generate_exit_enum(graph: TransitionGraph) -> str:
    """
    Generate exit enum from graph exits.

    Args:
        graph: Transition graph

    Returns:
        Exit enum class definition as string
    """
    class_name = _to_class_name(graph.entrypoint)
    lines = [
        f"class {class_name}Exit(ExitOutcome):",
        '    """Auto-generated exit enum for this workflow."""',
    ]

    for exit_def in graph.exits:
        enum_name = _to_exit_enum_name(exit_def.name)
        color = "green" if exit_def.code == 0 else "red"
        value = f"exit::{color}::{exit_def.name}"
        lines.append(f'    {enum_name} = "{value}"  # code={exit_def.code}')

    if len(lines) == 2:
        lines.append("    pass  # No exits defined")

    return "\n".join(lines)


def generate_transition_table(graph: TransitionGraph) -> str:
    """
    Generate transition table mapping state strings to next steps.

    Uses string keys for simplicity - matches Outcome-based API.

    Args:
        graph: Transition graph

    Returns:
        Transition table definition as string
    """
    lines = [
        "TRANSITION_TABLE: dict[str, Callable | str] = {",
    ]

    for transition in graph.transitions:
        # State string key: "node_name::outcome_type::detail"
        state_key = f"{transition.from_node}::{transition.from_state}"

        if transition.is_exit:
            # Exit: "exit::color::name"
            exit_name = transition.exit_name
            exit_def = graph.get_exit(exit_name) if exit_name else None
            if exit_def:
                color = "green" if exit_def.code == 0 else "red"
            else:
                color = "red"  # default to error
            target_ref = f'Exit.code("{color}", "{exit_name}")'
        else:
            # Find the node to get function name
            target_node = graph.get_node(transition.to_target)
            if target_node:
                target_ref = target_node.function
            else:
                target_ref = f'"# ERROR: unknown node {transition.to_target}"'

        lines.append(f'    "{state_key}": {target_ref},')

    lines.append("}")

    return "\n".join(lines)


def generate_metadata(graph: TransitionGraph, source_file: str) -> str:
    """
    Generate graph metadata dictionary.

    Args:
        graph: Transition graph
        source_file: Path to source YAML

    Returns:
        Metadata dictionary definition as string
    """
    max_iter = graph.options.max_iterations if graph.options else 100
    timestamp = datetime.now().isoformat()

    return f'''GRAPH_METADATA = {{
    "version": "{graph.version}",
    "entrypoint": "{graph.entrypoint}",
    "description": "{graph.description}",
    "source_file": "{source_file}",
    "generated_at": "{timestamp}",
    "start_node": "{graph.start_node}",
    "max_iterations": {max_iter},
}}'''


def _generate_helper_functions(class_name: str, start_function: str) -> str:
    """
    Generate helper functions.

    Args:
        class_name: PascalCase class name prefix
        start_function: Name of the start node function
    """
    state_class = f"{class_name}State"
    exit_class = f"{class_name}Exit"

    return f'''
def get_next_step(state: {state_class}) -> Callable | {exit_class}:
    """
    Get the next step for a given state.

    Args:
        state: Current state from node execution

    Returns:
        Next node function or exit code

    Raises:
        KeyError: If state is not in transition table
    """
    if state not in TRANSITION_TABLE:
        raise KeyError(f"未定義の状態です: {{state}}")
    return TRANSITION_TABLE[state]


def get_start_node() -> Callable:
    """Get the start node function."""
    return {start_function}
'''


def _to_enum_name(node_name: str, state: str) -> str:
    """
    Convert node name and state to enum member name.

    Example: ("fetch", "success::done") -> "FETCH_SUCCESS_DONE"
    """
    combined = f"{node_name}_{state}"
    return combined.upper().replace("::", "_").replace("-", "_")


def _to_class_name(entrypoint: str) -> str:
    """
    Convert entrypoint name to class name.

    Example: "my_workflow" -> "MyWorkflow"
    """
    return "".join(word.capitalize() for word in entrypoint.split("_"))


def _to_exit_enum_name(exit_name: str) -> str:
    """
    Convert exit name to enum member name.

    Example: "green_resolved" -> "GREEN_RESOLVED"
    """
    return exit_name.upper().replace("-", "_")
