"""
Main entry point and CLI argument parsing for the application.
"""

import sys
import time

from engine import config
from engine.display import BorderMode, DisplayRegistry
from engine.effects import get_registry
from engine.fetch import fetch_all, fetch_all_fast, fetch_poetry, load_cache, save_cache

# Import from sideline (the framework)
from sideline.pipeline import (
    Pipeline,
    PipelineConfig,
    PipelineContext,
    StageRegistry,
)
from sideline.pipeline.adapters import (
    CameraStage,
    DataSourceStage,
    EffectPluginStage,
    create_stage_from_display,
    create_stage_from_effect,
)
from sideline.pipeline.params import PipelineParams

# Import from engine (Mainline-specific)
from engine.pipeline.ui import UIConfig, UIPanel
from engine.pipeline.validation import validate_pipeline_config

try:
    from engine.display.backends.websocket import WebSocketDisplay
except ImportError:
    WebSocketDisplay = None

from .pipeline_runner import run_pipeline_mode


def _register_mainline_stages():
    """Register Mainline-specific stage components with Sideline.

    This should be called early in application startup to ensure
    all Mainline stages are available for pipeline construction.
    """
    try:
        from sideline.pipeline import StageRegistry

        # Method 1: Explicit registration via engine.plugins
        try:
            from engine.plugins import register_stages

            register_stages(StageRegistry)
        except ImportError as e:
            print(f"Warning: Failed to register Mainline stages: {e}")

        # Method 2: Register via plugin module (for entry point discovery)
        StageRegistry.register_plugin_module("engine.plugins")

        print("Mainline stage components registered successfully")
    except Exception as e:
        print(f"Warning: Stage registration failed: {e}")


def main():
    """Main entry point - all modes now use presets or CLI construction."""
    # Register Mainline stages with Sideline
    _register_mainline_stages()

    if config.PIPELINE_DIAGRAM:
        try:
            from sideline.pipeline import generate_pipeline_diagram
        except ImportError:
            print("Error: pipeline diagram not available")
            return
        print(generate_pipeline_diagram())
        return

    # Check for direct pipeline construction flags
    if "--pipeline-source" in sys.argv:
        # Construct pipeline directly from CLI args
        run_pipeline_mode_direct()
        return

    preset_name = None

    if config.PRESET:
        preset_name = config.PRESET
    elif config.PIPELINE_MODE:
        preset_name = config.PIPELINE_PRESET
    else:
        preset_name = "demo"

    available = list_presets()
    if preset_name not in available:
        print(f"Error: Unknown preset '{preset_name}'")
        print(f"Available presets: {', '.join(available)}")
        sys.exit(1)

    run_pipeline_mode(preset_name)


def run_pipeline_mode_direct():
    """Construct and run a pipeline directly from CLI arguments.

    Usage:
        python -m engine.app --pipeline-source headlines --pipeline-effects noise,fade --display null
        python -m engine.app --pipeline-source fixture --pipeline-effects glitch --pipeline-ui --display null

    Flags:
        --pipeline-source <source>: Headlines, fixture, poetry, empty, pipeline-inspect
        --pipeline-effects <effects>: Comma-separated list (noise, fade, glitch, firehose, hud, tint, border, crop)
        --pipeline-camera <type>: scroll, feed, horizontal, omni, floating, bounce
        --pipeline-display <display>: terminal, pygame, websocket, null, multi:term,pygame
        --pipeline-ui: Enable UI panel (BorderMode.UI)
        --pipeline-border <mode>: off, simple, ui
    """
    import engine.effects.plugins as effects_plugins
    from engine.camera import Camera
    from engine.data_sources.pipeline_introspection import PipelineIntrospectionSource
    from engine.data_sources.sources import EmptyDataSource, ListDataSource
    from engine.pipeline.adapters import (
        FontStage,
        ViewportFilterStage,
    )

    # Discover and register all effect plugins
    effects_plugins.discover_plugins()

    # Parse CLI arguments
    source_name = None
    effect_names = []
    camera_type = None
    display_name = None
    ui_enabled = False
    border_mode = BorderMode.OFF
    source_items = None
    allow_unsafe = False
    viewport_width = None
    viewport_height = None

    i = 1
    argv = sys.argv
    while i < len(argv):
        arg = argv[i]
        if arg == "--pipeline-source" and i + 1 < len(argv):
            source_name = argv[i + 1]
            i += 2
        elif arg == "--pipeline-effects" and i + 1 < len(argv):
            effect_names = [e.strip() for e in argv[i + 1].split(",") if e.strip()]
            i += 2
        elif arg == "--pipeline-camera" and i + 1 < len(argv):
            camera_type = argv[i + 1]
            i += 2
        elif arg == "--viewport" and i + 1 < len(argv):
            vp = argv[i + 1]
            try:
                viewport_width, viewport_height = map(int, vp.split("x"))
            except ValueError:
                print("Error: Invalid viewport format. Use WxH (e.g., 40x15)")
                sys.exit(1)
            i += 2
        elif arg == "--pipeline-display" and i + 1 < len(argv):
            display_name = argv[i + 1]
            i += 2
        elif arg == "--pipeline-ui":
            ui_enabled = True
            i += 1
        elif arg == "--pipeline-border" and i + 1 < len(argv):
            mode = argv[i + 1]
            if mode == "simple":
                border_mode = True
            elif mode == "ui":
                border_mode = BorderMode.UI
            else:
                border_mode = False
            i += 2
        elif arg == "--allow-unsafe":
            allow_unsafe = True
            i += 1
        else:
            i += 1

    if not source_name:
        print("Error: --pipeline-source is required")
        print(
            "Usage: python -m engine.app --pipeline-source <source> [--pipeline-effects <effects>] ..."
        )
        sys.exit(1)

    print("  \033[38;5;245mDirect pipeline construction\033[0m")
    print(f"  Source: {source_name}")
    print(f"  Effects: {effect_names}")
    print(f"  Camera: {camera_type}")
    print(f"  Display: {display_name}")
    print(f"  UI Enabled: {ui_enabled}")

    # Create initial config and params
    params = PipelineParams()
    params.source = source_name
    params.camera_mode = camera_type if camera_type is not None else ""
    params.effect_order = effect_names
    params.border = border_mode

    # Create minimal config for validation
    config_obj = PipelineConfig(
        source=source_name,
        display=display_name or "",  # Will be filled by validation
        camera=camera_type if camera_type is not None else "",
        effects=effect_names,
    )

    # Run MVP validation
    result = validate_pipeline_config(config_obj, params, allow_unsafe=allow_unsafe)

    if result.warnings and not allow_unsafe:
        print("  \033[38;5;226mWarning: MVP validation found issues:\033[0m")
        for warning in result.warnings:
            print(f"    - {warning}")

    if result.changes:
        print("  \033[38;5;226mApplied MVP defaults:\033[0m")
        for change in result.changes:
            print(f"    {change}")

    if not result.valid:
        print(
            "  \033[38;5;196mPipeline configuration invalid and could not be fixed\033[0m"
        )
        sys.exit(1)

    # Show MVP summary
    print("  \033[38;5;245mMVP Configuration:\033[0m")
    print(f"    Source: {result.config.source}")
    print(f"    Display: {result.config.display}")
    print(f"    Camera: {result.config.camera or 'static (none)'}")
    print(f"    Effects: {result.config.effects if result.config.effects else 'none'}")
    print(f"    Border: {result.params.border}")

    # Load source items
    if source_name == "headlines":
        cached = load_cache()
        if cached:
            source_items = cached
        else:
            source_items = fetch_all_fast()
            if source_items:
                import threading

                def background_fetch():
                    full_items, _, _ = fetch_all()
                    save_cache(full_items)

                background_thread = threading.Thread(
                    target=background_fetch, daemon=True
                )
                background_thread.start()
    elif source_name == "fixture":
        source_items = load_cache()
        if not source_items:
            print("  \033[38;5;196mNo fixture cache available\033[0m")
            sys.exit(1)
    elif source_name == "poetry":
        source_items, _, _ = fetch_poetry()
    elif source_name == "empty" or source_name == "pipeline-inspect":
        source_items = []
    else:
        print(f"  \033[38;5;196mUnknown source: {source_name}\033[0m")
        sys.exit(1)

    if source_items is not None:
        print(f"  \033[38;5;82mLoaded {len(source_items)} items\033[0m")

    # Set border mode
    if ui_enabled:
        border_mode = BorderMode.UI

    # Build pipeline using validated config and params
    params = result.params
    params.viewport_width = viewport_width if viewport_width is not None else 80
    params.viewport_height = viewport_height if viewport_height is not None else 24

    ctx = PipelineContext()
    ctx.params = params

    # Create display using validated display name
    display_name = result.config.display or "terminal"  # Default to terminal if empty

    # Warn if display was auto-selected (not explicitly specified)
    if not display_name:
        print(
            "  \033[38;5;226mWarning: No --pipeline-display specified, using default: terminal\033[0m"
        )
        print(
            "  \033[38;5;245mTip: Use --pipeline-display null for headless mode (useful for testing)\033[0m"
        )

    display = DisplayRegistry.create(display_name)

    # Set positioning mode
    if "--positioning" in sys.argv:
        idx = sys.argv.index("--positioning")
        if idx + 1 < len(sys.argv):
            params.positioning = sys.argv[idx + 1]
    if not display:
        print(f"  \033[38;5;196mFailed to create display: {display_name}\033[0m")
        sys.exit(1)
    display.init(0, 0)

    # Create pipeline using validated config
    pipeline = Pipeline(config=result.config, context=ctx)

    # Add stages
    # Source stage
    if source_name == "pipeline-inspect":
        introspection_source = PipelineIntrospectionSource(
            pipeline=None,
            viewport_width=params.viewport_width,
            viewport_height=params.viewport_height,
        )
        pipeline.add_stage(
            "source", DataSourceStage(introspection_source, name="pipeline-inspect")
        )
    elif source_name == "empty":
        empty_source = EmptyDataSource(
            width=params.viewport_width, height=params.viewport_height
        )
        pipeline.add_stage("source", DataSourceStage(empty_source, name="empty"))
    else:
        list_source = ListDataSource(source_items, name=source_name)
        pipeline.add_stage("source", DataSourceStage(list_source, name=source_name))

    # Add viewport filter and font for headline sources
    if source_name in ["headlines", "poetry", "fixture"]:
        pipeline.add_stage(
            "viewport_filter", ViewportFilterStage(name="viewport-filter")
        )
        pipeline.add_stage("font", FontStage(name="font"))
    else:
        # Fallback to simple conversion for other sources
        from engine.pipeline.adapters import SourceItemsToBufferStage

        pipeline.add_stage("render", SourceItemsToBufferStage(name="items-to-buffer"))

    # Add camera
    speed = getattr(params, "camera_speed", 1.0)
    camera = None
    if camera_type == "feed":
        camera = Camera.feed(speed=speed)
    elif camera_type == "scroll":
        camera = Camera.scroll(speed=speed)
    elif camera_type == "horizontal":
        camera = Camera.horizontal(speed=speed)
    elif camera_type == "omni":
        camera = Camera.omni(speed=speed)
    elif camera_type == "floating":
        camera = Camera.floating(speed=speed)
    elif camera_type == "bounce":
        camera = Camera.bounce(speed=speed)

    if camera:
        pipeline.add_stage("camera", CameraStage(camera, name=camera_type))

    # Add effects
    effect_registry = get_registry()
    for effect_name in effect_names:
        effect = effect_registry.get(effect_name)
        if effect:
            pipeline.add_stage(
                f"effect_{effect_name}", create_stage_from_effect(effect, effect_name)
            )

    # Add display
    pipeline.add_stage("display", create_stage_from_display(display, display_name))

    pipeline.build()

    if not pipeline.initialize():
        print("  \033[38;5;196mFailed to initialize pipeline\033[0m")
        sys.exit(1)

    # Create UI panel if border mode is UI
    ui_panel = None
    if params.border == BorderMode.UI:
        ui_panel = UIPanel(UIConfig(panel_width=24, start_with_preset_picker=True))
        # Enable raw mode for terminal input if supported
        if hasattr(display, "set_raw_mode"):
            display.set_raw_mode(True)
        for stage in pipeline.stages.values():
            if isinstance(stage, EffectPluginStage):
                effect = stage._effect
                enabled = effect.config.enabled if hasattr(effect, "config") else True
                stage_control = ui_panel.register_stage(stage, enabled=enabled)
                stage_control.effect = effect  # type: ignore[attr-defined]

        if ui_panel.stages:
            first_stage = next(iter(ui_panel.stages))
            ui_panel.select_stage(first_stage)
            ctrl = ui_panel.stages[first_stage]
            if hasattr(ctrl, "effect"):
                effect = ctrl.effect
                if hasattr(effect, "config"):
                    config = effect.config
                    try:
                        import dataclasses

                        if dataclasses.is_dataclass(config):
                            for field_name, field_obj in dataclasses.fields(config):
                                if field_name == "enabled":
                                    continue
                                value = getattr(config, field_name, None)
                                if value is not None:
                                    ctrl.params[field_name] = value
                                    ctrl.param_schema[field_name] = {
                                        "type": type(value).__name__,
                                        "min": 0
                                        if isinstance(value, (int, float))
                                        else None,
                                        "max": 1 if isinstance(value, float) else None,
                                        "step": 0.1 if isinstance(value, float) else 1,
                                    }
                    except Exception:
                        pass

    # Run pipeline loop
    from engine.display import render_ui_panel

    ctx.set("display", display)
    ctx.set("items", source_items)
    ctx.set("pipeline", pipeline)
    ctx.set("pipeline_order", pipeline.execution_order)

    current_width = params.viewport_width
    current_height = params.viewport_height

    # Only get dimensions from display if viewport wasn't explicitly set
    if "--viewport" not in sys.argv and hasattr(display, "get_dimensions"):
        current_width, current_height = display.get_dimensions()
        params.viewport_width = current_width
        params.viewport_height = current_height

    print("  \033[38;5;82mStarting pipeline...\033[0m")
    print("  \033[38;5;245mPress Ctrl+C to exit\033[0m\n")

    try:
        frame = 0
        while True:
            params.frame_number = frame
            ctx.params = params

            result = pipeline.execute(source_items)
            if not result.success:
                error_msg = f" ({result.error})" if result.error else ""
                print(f"  \033[38;5;196mPipeline execution failed{error_msg}\033[0m")
                break

            # Render with UI panel
            if ui_panel is not None:
                buf = render_ui_panel(
                    result.data, current_width, current_height, ui_panel
                )
                display.show(buf, border=False)
            else:
                display.show(result.data, border=border_mode)

            # Handle keyboard events if UI is enabled
            if ui_panel is not None:
                # Try pygame first
                if hasattr(display, "_pygame"):
                    try:
                        import pygame

                        for event in pygame.event.get():
                            if event.type == pygame.KEYDOWN:
                                ui_panel.process_key_event(event.key, event.mod)
                    except (ImportError, Exception):
                        pass
                # Try terminal input
                elif hasattr(display, "get_input_keys"):
                    try:
                        keys = display.get_input_keys()
                        for key in keys:
                            ui_panel.process_key_event(key, 0)
                    except Exception:
                        pass

            # Check for quit request
            if hasattr(display, "is_quit_requested") and display.is_quit_requested():
                if hasattr(display, "clear_quit_request"):
                    display.clear_quit_request()
                raise KeyboardInterrupt()

            time.sleep(1 / 60)
            frame += 1

    except KeyboardInterrupt:
        pipeline.cleanup()
        display.cleanup()
        print("\n  \033[38;5;245mPipeline stopped\033[0m")
        return

    pipeline.cleanup()
    display.cleanup()
    print("\n  \033[38;5;245mPipeline stopped\033[0m")
