import 'dart:async';

import 'package:flutter/material.dart';
import 'package:iop_console/iop_console.dart';

import 'client_config.dart';
import 'control_plane_status_client.dart';
import 'control_plane_status_controller.dart';
import 'control_plane_status_widgets.dart';
import 'iop_wire/client_wire_client.dart';
import 'main.dart';
import 'src/integrations/nexo/nexo_notification_host_integration.dart';

class ClientHomePage extends StatefulWidget {
  final ClientWireClient? testClient;
  final NexoNotificationHostIntegration? notificationHost;
  final ControlPlaneStatusRepository? statusRepository;

  const ClientHomePage({
    super.key,
    this.testClient,
    this.notificationHost,
    this.statusRepository,
  });

  @override
  State<ClientHomePage> createState() => _ClientHomePageState();
}

class _ClientHomePageState extends State<ClientHomePage> {
  String _wireStatus = 'Disconnected';
  ClientWireClient? _client;
  StreamSubscription? _notificationSubscription;
  HttpControlPlaneStatusRepository? _ownedStatusRepository;

  late final ControlPlaneStatusController _controller;

  @override
  void initState() {
    super.initState();
    final ownedStatusRepository = widget.statusRepository == null
        ? HttpControlPlaneStatusRepository(
            baseUrl: ClientConfig.controlPlaneHttpUrl,
          )
        : null;
    _ownedStatusRepository = ownedStatusRepository;
    final statusRepo = widget.statusRepository ?? ownedStatusRepository!;
    _controller = ControlPlaneStatusController(statusRepository: statusRepo);

    // Auto-connect on start
    WidgetsBinding.instance.addPostFrameCallback((_) {
      applyFullscreenMode();
      _connectAndFetch();
    });

    final host = widget.notificationHost;
    if (host != null) {
      _notificationSubscription = host.onNotification.listen(
        _showNexoNotification,
      );
    }
  }

  @override
  void dispose() {
    _notificationSubscription?.cancel();
    _client?.close();
    _controller.dispose();
    _ownedStatusRepository?.close();
    super.dispose();
  }

  void _showNexoNotification(Map<String, dynamic> data) {
    if (data['type'] != 'message' || !mounted) return;

    final message = data['message'] as String? ?? '';
    final channel = data['channel_name'] as String? ?? '';
    final sender = data['sender_name'] as String? ?? '';
    final content = sender.isNotEmpty
        ? '[$channel] $sender: $message'
        : message;

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(content, maxLines: 2, overflow: TextOverflow.ellipsis),
        duration: const Duration(seconds: 4),
        behavior: SnackBarBehavior.floating,
      ),
    );
  }

  Future<void> _connectAndFetch() async {
    await Future.wait([_connectWire(), _controller.fetchEdges()]);
  }

  Future<void> _connectWire() async {
    if (!mounted) return;
    setState(() {
      _wireStatus = 'Connecting';
    });

    try {
      ClientWireClient client;
      if (widget.testClient != null) {
        client = widget.testClient!;
      } else {
        client = await ClientWireClient.connectToUrl(
          ClientConfig.controlPlaneWireUrl,
        );
      }
      _client = client;

      // Hello handshake
      final response = await client.hello(
        clientId: 'client-ui',
        clientVersion: '1.0.0',
      );

      if (!mounted) return;
      setState(() {
        _wireStatus = response.ready ? 'Connected' : 'Error';
      });

      client.addDisconnectListener((_) {
        if (!mounted) return;
        setState(() {
          _wireStatus = 'Disconnected';
          _client = null;
        });
      });
    } catch (e) {
      if (!mounted) return;
      setState(() {
        _wireStatus = 'Error';
        _client = null;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: _controller,
      builder: (context, _) {
        final config = IopConsoleConfig(
          controlPlaneHttpUrl: ClientConfig.controlPlaneHttpUrl,
          controlPlaneWireUrl: ClientConfig.controlPlaneWireUrl,
        );

        final currentEdgeId = _controller.selectedEdgeId;

        return IopConsoleShell(
          config: config,
          overview: IopConsoleOverview(
            config: config,
            statusText: _wireStatus,
            onRefresh: _connectAndFetch,
          ),
          edges: EdgesPanel(
            edges: _controller.edges,
            isLoading: _controller.loadingEdges,
            error: _controller.edgesError,
            selectedEdgeId: currentEdgeId,
            onSelectEdge: _controller.selectEdge,
            onRefresh: _controller.fetchEdges,
          ),
          nodes: NodesPanel(
            edges: _controller.edges,
            selectedEdgeId: currentEdgeId,
            selectedEdgeStatus: currentEdgeId != null
                ? _controller.edgeStatuses[currentEdgeId]
                : null,
            isLoading: currentEdgeId != null
                ? (_controller.loadingStatuses[currentEdgeId] ?? false)
                : false,
            error: currentEdgeId != null
                ? _controller.statusErrors[currentEdgeId]
                : null,
            onSelectEdge: _controller.selectEdge,
            onRefresh: () {
              if (currentEdgeId != null) {
                _controller.fetchEdgeStatus(currentEdgeId);
              }
            },
          ),
          runtime: RuntimePanel(
            edges: _controller.edges,
            selectedEdgeId: currentEdgeId,
            onSelectEdge: _controller.selectEdge,
            statusRepository: _controller.statusRepository,
          ),
          executionLogs: ExecutionLogsPanel(
            edges: _controller.edges,
            selectedEdgeId: currentEdgeId,
            events: currentEdgeId != null
                ? (_controller.edgeEvents[currentEdgeId] ?? [])
                : [],
            isLoading: currentEdgeId != null
                ? (_controller.loadingEvents[currentEdgeId] ?? false)
                : false,
            error: currentEdgeId != null
                ? _controller.eventErrors[currentEdgeId]
                : null,
            onSelectEdge: _controller.selectEdge,
            onRefresh: () {
              if (currentEdgeId != null) {
                _controller.fetchEdgeEvents(currentEdgeId);
              }
            },
          ),
        );
      },
    );
  }
}
