"""Client screen for interacting with {{stinger.name|UpperCamelCase}}IPC server."""

import concurrent.futures as futures
from datetime import datetime, timedelta
import isodate
from typing import List, Optional, Any, Dict
from textual.app import ComposeResult # typing: ignore
from textual.screen import Screen, ModalScreen # typing: ignore
from textual.widgets import Header, Footer, Static, RichLog, Button, Input, Label, Select # typing: ignore
from textual.containers import Horizontal, VerticalScroll, Vertical # typing: ignore
from {{stinger.name|loweronly}}ipc.interface_types import *
from {{stinger.name|loweronly}}ipc.client import {{stinger.name|UpperCamelCase}}Client

class PropertyEditModal(ModalScreen[bool]):
    """Modal screen for editing a property value."""
    
    CSS = """
    PropertyEditModal {
        align: center middle;
    }
    
    #property_modal_container {
        width: 60%;
        height: auto;
        background: $surface;
        border: thick $primary;
        padding: 1;
    }
    
    #property_modal_title {
        text-style: bold;
        text-align: center;
        background: $primary;
        padding: 1;
        margin-bottom: 1;
    }
    
    .property_input_label {
        margin-top: 1;
        margin-bottom: 1;
    }

    .property_input_value_label {
        margin-top: 1;
        margin-bottom: 1;
        color: $primary;
    }
    
    #property_input {
        margin-bottom: 1;
    }

    .property_input_value {
        margin-bottom: 1;
    }
    
    #property_button_container {
        layout: horizontal;
        height: auto;
        margin-top: 1;
    }
    
    #property_button_container Button {
        width: 1fr;
        margin: 0 1;
    }
    """
    
    def __init__(self, property_name: str, current_value: Any, client: {{stinger.name|UpperCamelCase}}Client):
        super().__init__()
        self.property_name = property_name
        self.current_value = current_value
        self.client = client
    
    def compose(self) -> ComposeResult:
        """Compose the modal screen."""
        with Vertical(id="property_modal_container"):
            yield Static(f"Edit: {self.property_name}", id="property_modal_title")
            yield Label(f"Current value: {self.current_value}", classes="property_input_label")
            {%for prop_name, prop in stinger.properties.items()-%}
            if self.property_name == '{{prop_name|snake_case}}':
                {%-if prop.arg_list|length == 1%}
                {%if prop.arg_list[0].arg_type.name == 'ENUM'%}
                enum_options = [
                    (f"{member.name} ({member.value})", member.value)
                    for member in {{prop.arg_list[0].enum.python_type}}
                ]
                yield Select(options=enum_options, value=self.current_value.value if self.current_value is not None else None, id="property_input")
                {%else%}{# not single enum arg #}
                yield Input(placeholder=f"Enter new value", value=str(self.current_value) if self.current_value is not None else "", id="property_input")
                {%endif%}{# single arg type cases #}
                {%else%}{# multiple args #}
                {%-for arg in prop.arg_list%}
                    yield Label(f"{{arg.name}}{%if arg.arg_type.name == 'STRUCT'%} (JSON){%endif%}", classes="property_input_value_label")
                    yield Input(placeholder=f"{{arg.name}} value", value={%if arg.arg_type.name == 'STRUCT'%}self.current_value.{{arg.name|snake_case}}.model_dump_json(){%else%}str(self.current_value.{{arg.name|snake_case}}){%endif%}, classes="property_input_value", id="property_input_{{arg.name|snake_case}}")
                {%endfor-%}
                {%endif%}
            {%endfor%}
            
            with Horizontal(id="property_button_container"):
                yield Button("Update", variant="primary", id="update_button")
                yield Button("Cancel", variant="error", id="cancel_button")
    
{%macro from_input_string(arg, value)%}
{%-if arg.arg_type.name == 'PRIMITIVE'%}
{%-if arg.type.name == 'BOOLEAN'%}{{value}}.lower() in ('true', '1', 'yes', 'y')
{%-elif arg.type.name == 'INTEGER'%}int({{value}})
{%-elif arg.type.name == 'FLOAT'%}float({{value}})
{%-elif arg.type.name == 'STRING'%}str({{value}})
{%-else%}{{value}}{%endif%}
{%-elif arg.arg_type.name == 'ENUM'%}{{arg.enum.python_type}}({{value}})
{%-elif arg.arg_type.name == 'STRUCT'%}{{arg.struct.python_type}}.model_validate_json({{value}})
{%-elif arg.arg_type.name == 'DATETIME'%}datetime.fromisoformat({{value}})
{%-elif arg.arg_type.name == 'DURATION'%}isodate.parse_duration({{value}})
{%-elif arg.arg_type.name == 'BINARY'%}{{value}}.encode('utf-8')
{%-elif arg.arg_type.name == 'ARRAY'%}[{{from_input_string(arg.element, 'v')}} for v in {{value}}.split(',')]
{%-endif%}{%if arg.optional%} if {{value}} else None{%endif%}
{%-endmacro%}

    def on_button_pressed(self, event: Button.Pressed) -> None:
        """Handle button presses."""
        if event.button.id == "update_button":
            try:
                {%for prop_name, prop in stinger.properties_rw.items()-%}
                {%-if not loop.first%}el{%endif%}if self.property_name == '{{prop_name|snake_case}}':
                    {%-if prop.arg_list|length == 1%}
                    input_widget = self.query_one("#property_input", Input)
                    new_{{prop_name|snake_case}}_value = {{from_input_string(prop.arg_list[0], "input_widget.value")}}
                    {%else%}
                    {%-for arg in prop.arg_list%}
                    input_widget_{{arg.name|snake_case}} = self.query_one("#property_input_{{arg.name|snake_case}}", Input)
                    new_value_{{arg.name|snake_case}} = {{from_input_string(arg, "input_widget_" + arg.name|snake_case + ".value")}}
                    {%endfor%}
                    new_{{prop_name|snake_case}}_value = {{prop_name|UpperCamelCase}}Property(
                        {%-for arg in prop.arg_list%}
                        {{arg.name|snake_case}}=new_value_{{arg.name|snake_case}},
                        {%-endfor%}
                    )
                    {%endif%}
                    self.client.{{prop_name|snake_case}} = new_{{prop_name|snake_case}}_value
                {%endfor%}
            
                self.dismiss(True)
            except Exception as e:
                self.app.notify(f"Error updating property: {e}", severity="error")
        else:
            self.dismiss(False)


class MethodCallModal(ModalScreen[Optional[str]]):
    """Modal screen for calling a method with inputs."""
    
    CSS = """
    MethodCallModal {
        align: center middle;
    }
    
    #modal_container {
        width: 80;
        height: auto;
        max-height: 90%;
        background: $surface;
        border: thick $primary;
        padding: 1;
    }
    
    #modal_title {
        text-style: bold;
        text-align: center;
        background: $primary;
        padding: 1;
        margin-bottom: 1;
    }
    
    #inputs_container {
        height: auto;
        max-height: 60%;
        overflow-y: auto;
    }
    
    .input_label {
        margin-top: 1;
        margin-bottom: 1;
    }
    
    Input {
        margin-bottom: 1;
    }
    
    #button_container {
        layout: horizontal;
        height: auto;
        margin-top: 1;
    }
    
    #button_container Button {
        width: 1fr;
        margin: 0 1;
    }
    
    #result_text {
        margin-top: 1;
        padding: 1;
        background: $panel;
        border: solid $accent;
        max-height: 20;
        overflow-y: auto;
    }
    """
    
    def __init__(self, method_name: str, params: Dict[str, type], client: Any):
        super().__init__()
        self.method_name = method_name
        self.params = params
        self.client = client
        self.result_widget: Optional[Static] = None
    
    def compose(self) -> ComposeResult:
        """Compose the modal screen."""
        with Vertical(id="modal_container"):
            yield Static(f"Call: {self.method_name}", id="modal_title")
            
            with VerticalScroll(id="inputs_container"):
                if self.params:
                    for param_name, param_type in self.params.items():
                        yield Label(f"{param_name} ({param_type.__name__ if hasattr(param_type, '__name__') else str(param_type)}):", classes="input_label")
                        yield Input(placeholder=f"Enter {param_name}", id=f"input_{param_name}")
                else:
                    yield Static("No parameters required")
            
            with Horizontal(id="button_container"):
                yield Button("Call Method", variant="primary", id="call_button")
                yield Button("Close", variant="error", id="cancel_button")
            
            self.result_widget = Static("", id="result_text")
            yield self.result_widget
    
    def on_button_pressed(self, event: Button.Pressed) -> None:
        """Handle button presses."""
        if event.button.id == "cancel_button":
            self.dismiss(None)
        elif event.button.id == "call_button":
            self._call_method()
    
    def _call_method(self) -> None:
        """Call the method with collected inputs."""
        assert self.result_widget is not None, "result_widget must be initialized"
        try:
            # Collect inputs
            kwargs = {}
            for param_name, param_type in self.params.items():
                input_widget = self.query_one(f"#input_{param_name}", Input)
                value_str = input_widget.value
                
                # Simple parsing - for demo, handle basic types
                if value_str:
                    kwargs[param_name] = self._parse_value(value_str, param_type)
                elif "Optional" not in str(param_type):
                    self.result_widget.update("[red]Error: Missing required parameter[/red]")
                    return
                else:
                    kwargs[param_name] = None
            
            # Call the method
            self.result_widget.update("[yellow]Calling method...[/yellow]")
            method = getattr(self.client, self.method_name)
            future_result = method(**kwargs)
            
            # Wait for result (with timeout)
            try:
                result = future_result.result(timeout=5.0)
                self.result_widget.update(f"[green]Success![/green]\n\nResult: {result}")
            except futures.TimeoutError:
                self.result_widget.update("[red]Timeout waiting for response[/red]")
            except Exception as e:
                self.result_widget.update(f"[red]Error: {e}[/red]")
                
        except Exception as e:
            self.result_widget.update(f"[red]Error preparing call: {e}[/red]")
    
    def _parse_value(self, value_str: str, param_type: type) -> Any:
        """Parse a string value to the appropriate type."""
        type_str = str(param_type)
        
        if "int" in type_str.lower():
            return int(value_str)
        elif "float" in type_str.lower() or "number" in type_str.lower():
            return float(value_str)
        elif "str" in type_str.lower():
            return value_str
        elif "bool" in type_str.lower():
            return value_str.lower() in ("true", "1", "yes")
        else:
            # For complex types, return the string for now
            return value_str


class ClientScreen(Screen):
    """Screen for interacting with a connected {{stinger.name|UpperCamelCase}}IPC server."""
    
    CSS = """
    ClientScreen {
        layout: vertical;
    }
    
    #main_container {
        width: 100%;
        height: 1fr;
    }
    
    #left_pane {
        width: 20%;
        border-right: solid $primary;
        padding: 1;
    }
    
    #middle_pane {
        width: 30%;
        border-right: solid $primary;
        padding: 1;
    }
    
    #right_pane {
        width: 50%;
        padding: 1;
    }
    
    .pane_title {
        text-style: bold;
        background: $primary;
        padding: 1;
        text-align: center;
        margin-bottom: 1;
    }
    
    RichLog {
        height: 1fr;
        border: solid $accent;
    }
    
    .property_item {
        margin: 0;
        padding: 1;
        background: $surface;
        border: solid $accent;
    }
    
    .property_item.writable {
        background: $surface;
    }
    
    .property_item.writable:hover {
        background: $accent 20%;
    }
    
    .property_item.readonly {
        background: $surface-darken-1;
        color: $text-muted;
    }
    
    .property_name {
        text-style: bold;
        color: $accent;
    }
    
    .property_value {
        margin-top: 1;
        color: $text;
    }
    """
    
    BINDINGS = [
        ("escape", "back_to_discovery", "Back to Discovery"),
    ]
    
    def __init__(self):
        """Initialize the client screen."""
        super().__init__()
        self.client = None
    
    def compose(self) -> ComposeResult:
        """Compose the client screen widgets."""
        yield Header()
        with Horizontal(id="main_container"):
            with VerticalScroll(id="left_pane"):
                yield Static("Methods", classes="pane_title")
                # Method buttons will be added dynamically
            with VerticalScroll(id="middle_pane"):
                yield Static("Properties", classes="pane_title")
                # Properties will be added dynamically
            with VerticalScroll(id="right_pane"):
                yield Static("Signals", classes="pane_title")
                yield RichLog(id="signals_log", highlight=True, markup=True)
        yield Footer()
    
    def on_mount(self) -> None:
        """Set up signal handlers when screen mounts."""
        # Get the client from the app
        self.client = self.app.{{stinger.name|snake_case}}_client
        
        if self.client is None:
            self.notify("No client available!", severity="error")
            return
        
        # Add method buttons
        self._add_method_buttons()
        
        # Register all signal handlers
        self._register_signal_handlers()
        
        # Register all property handlers
        self._register_property_handlers()
    
    def _add_method_buttons(self) -> None:
        """Add buttons for all call_* methods."""
        pane = self.query_one("#left_pane", VerticalScroll)
        
        # Define all methods with their parameters
        methods = {
        {%-for method_name, method in stinger.methods.items()%}
            "{{method_name|snake_case}}": { {% for arg in method.arg_list %}"{{arg.name|snake_case}}": {{arg.python_annotation}}, {%endfor%} }, 
        {%endfor%}
        } # type: Dict[str, Dict[str, Any]]
        
        for method_name, params in methods.items():
            btn = Button(method_name, classes="method_button")
            btn.method_name = method_name  # Store for retrieval
            btn.method_params = params  
            pane.mount(btn)
    
    def on_button_pressed(self, event: Button.Pressed) -> None:
        """Handle method button presses."""
        if hasattr(event.button, 'method_name'):
            method_name = event.button.method_name
            method_params = event.button.method_params
            
            # Show modal for method call
            modal = MethodCallModal(method_name, method_params, self.client)
            self.app.push_screen(modal)
    
    def _register_signal_handlers(self) -> None:
        """Register callbacks for all receive_* methods."""
        log = self.query_one("#signals_log", RichLog)
        
        # Define a generic handler factory
        def make_handler(signal_name: str):
            def handler(*args, **kwargs):
                # Format the received data
                if args:
                    data = ", ".join([f"{arg}" for arg in args])
                elif kwargs:
                    data = ", ".join([f"{k}={v}" for k, v in kwargs.items()])
                else:
                    data = "(no data)"
                
                # Log to the RichLog widget
                timestamp = datetime.now().strftime("%H:%M:%S")
                log.write(f"[gray]{timestamp}[/gray] [bold cyan]{signal_name}[/bold cyan]: {data}")
            return handler
        
        # Register all signal handlers
        assert self.client is not None, "Client must be initialized"
        {%-for signal_name, signal in stinger.signals.items()%}
        self.client.receive_{{signal_name|snake_case}}(make_handler("{{signal_name|snake_case}}"))
        {%-endfor%}
    
    def _register_property_handlers(self) -> None:
        """Register callbacks for all *_changed methods and create property displays."""
        pane = self.query_one("#middle_pane", VerticalScroll)
        
        # Define property registration helper
        def register_property(prop_name: str, changed_method_name: str, is_writable: bool = False):
            # Create a Static widget for this property
            prop_widget = Static(id=f"prop_{prop_name}")
            prop_widget.add_class("property_item")
            
            # Add writable or readonly class
            if is_writable:
                prop_widget.add_class("writable")
                prop_widget.can_focus = True
                prop_widget.property_name = prop_name# Store for click handling
            else:
                prop_widget.add_class("readonly")
            
            pane.mount(prop_widget)
            
            {%for prop_name, prop in stinger.properties.items() %}
            {%if not loop.first%}el{%endif%}if prop_name == "{{prop_name|snake_case}}":
                def on_{{prop_name|snake_case}}_updated(value: {{prop.python_annotation}}):
                    prop_widget.current_value = value
                    
                    values = []
                    {%if prop.arg_list|length == 1%}
                    {%if prop.arg_list[0].arg_type.name == 'DATETIME'%}
                    values.append(f"{value.isoformat() if value else 'None'}")
                    {%elif prop.arg_list[0].arg_type.name == 'ENUM'%}
                    values.append(f"{value.name if value else 'None'} ({value.value if value else 'None'})")
                    {%elif prop.arg_list[0].arg_type.name == 'STRUCT'%}
                    {%for mem in prop.arg_list[0].struct.members %}
                    values.append(f"[bold]{{prop.arg_list[0].name}}.{{mem.name}}[/bold]: { value.{{mem.name|snake_case}} }") 
                    {%endfor%}
                    {%else%}
                    values.append(f"{value}")  # {{prop.arg_list[0].arg_type.name}}
                    {%endif%}
                    {%else%}
                    {%- for arg in prop.arg_list %}
                    {%if arg.arg_type.name == 'DATETIME'%}
                    line = (f"[bold]{{arg.name}}[/bold]: { value.{{arg.name}}.isoformat() if value.{{arg.name|snake_case}} else 'None' }")
                    values.append(line)
                    {%elif arg.arg_type.name == 'ENUM'%}
                    line = (f"[bold]{{arg.name}}[/bold]: { value.{{arg.name}}.name if value.{{arg.name|snake_case}} else 'None' } ({ value.{{arg.name}}.value if value.{{arg.name|snake_case}} else 'None' })")
                    values.append(line)
                    {%elif arg.arg_type.name == 'STRUCT'%}
                    {%for mem in arg.struct.members %}
                    values.append(f"[bold]{{arg.name}}.{{mem.name}}[/bold]: { value.{{arg.name|snake_case}}.{{mem.name|snake_case}} }") 
                    {%endfor%}
                    {%else%}
                    line = (f"[bold]{{arg.name}}[/bold]: { value.{{arg.name|snake_case}} }") # {{arg.arg_type.name}}
                    values.append(line)
                    {%endif%}
                    {%- endfor %}
                    {%endif%}
                    value_str = "\n".join(values)
                    
                    # Update the widget
                    prop_widget.update(
                        f"[bold cyan]{prop_name}[/bold cyan]\n{value_str}"
                    )

                # Register the handler with call_immediately=True
                assert self.client is not None, "Client must be initialized"
                self.client.{{prop_name|snake_case}}_changed(on_{{prop_name|snake_case}}_updated, call_immediately=True)
            {%endfor%}
            

        
        # Register all properties
        {%-for property_name, property in stinger.properties.items()%}
        register_property("{{property_name|snake_case}}", "{{property_name|snake_case}}_changed", is_writable={{(not property.read_only) and 'True' or 'False'}})
        {%-endfor%}
    
    def on_click(self, event) -> None:
        """Handle clicks on property widgets."""
        assert self.client is not None, "Client must be initialized"

        # Check if the clicked widget is a writable property
        widget = event.widget
        if hasattr(widget, 'property_name') and hasattr(widget, 'current_value'):
            property_name = widget.property_name
            current_value = widget.current_value
            
            # Open the edit modal
            modal = PropertyEditModal(property_name, current_value, self.client)
            self.app.push_screen(modal)
    
    def action_back_to_discovery(self) -> None:
        """Navigate back to the discovery screen."""
        self.app.pop_screen()
