"""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
from textual.screen import Screen, ModalScreen
from textual.widgets import Header, Footer, Static, RichLog, Button, Input, Label
from textual.containers import Horizontal, VerticalScroll, Vertical
from {{stinger.name|snake_case}}ipc.interface_types import *
from {{stinger.name|snake_case}}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%}
                yield Input(placeholder=f"Enter new value", value=str(self.current_value) if self.current_value is not None else "", id="property_input")
                {%else%}
                {%-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%}
        }
        
        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
        {%-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
                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."""
        # 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()
