import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:iop_client/control_plane_status_client.dart';
import 'package:iop_client/main.dart';
import 'package:iop_client/widgets/edges_panel.dart';
import 'package:iop_client/widgets/nodes_panel.dart';
import 'support/client_test_harness.dart';

void main() {
  testWidgets('Client App opens Edges panel and displays Edge details', (
    WidgetTester tester,
  ) async {
    final fakeClient = FakeClientWireClient(shouldSuccess: true);
    final fakeStatusRepo = FakeControlPlaneStatusRepository();

    await tester.pumpWidget(
      IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo),
    );
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    await tester.tap(find.byTooltip('Edges'));
    await tester.pump();

    // Verify list of edges
    expect(find.text('Edge Alpha'), findsNWidgets(2));
    expect(find.text('Edge Beta'), findsOneWidget);

    // Verify detail pane for the first auto-selected edge (Edge Alpha)
    expect(find.text('Edge ID'), findsOneWidget);
    expect(find.text('edge-a'), findsOneWidget);
    expect(find.text('1.2.0'), findsOneWidget);
    expect(find.text('Serving'), findsOneWidget);
    expect(find.text('Automation'), findsOneWidget);

    // Verify selecting another Edge switches the detail pane instead of
    // reusing the first Edge's status view.
    await tester.tap(find.text('Edge Beta'));
    await tester.pump();

    expect(find.text('Edge Alpha'), findsOneWidget);
    expect(find.text('Edge Beta'), findsNWidgets(2));
    expect(find.text('edge-b'), findsOneWidget);
    expect(find.text('1.2.1'), findsOneWidget);
    expect(find.text('DISCONNECTED'), findsOneWidget);
    expect(find.text('unhealthy'), findsOneWidget);
  });

  testWidgets(
    'Client App opens Nodes panel and displays active Nodes and configurations',
    (WidgetTester tester) async {
      final fakeClient = FakeClientWireClient(shouldSuccess: true);
      final fakeStatusRepo = FakeControlPlaneStatusRepository();

      await tester.pumpWidget(
        IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo),
      );
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 100));

      await tester.tap(find.byTooltip('Nodes'));
      await tester.pumpAndSettle();

      // Verify Nodes view header
      expect(find.text('Nodes View'), findsOneWidget);

      // Verify nodes listed
      expect(find.text('Node One'), findsOneWidget);
      expect(find.text('Label: GPU-T4'), findsOneWidget);
      expect(find.text('Concurrency Limit: 4'), findsOneWidget);
      expect(find.text('ollama'), findsOneWidget);
      expect(find.text('custom'), findsOneWidget);
    },
  );

  testWidgets(
    'Client App Nodes panel displays Provider Catalog for nodes with snapshots',
    (WidgetTester tester) async {
      final fakeClient = FakeClientWireClient(shouldSuccess: true);
      final fakeStatusRepo = FakeControlPlaneStatusRepository();

      await tester.pumpWidget(
        IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo),
      );
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 100));

      await tester.tap(find.byTooltip('Nodes'));
      await tester.pumpAndSettle();

      // Scroll to reveal second node's provider catalog before assertions
      await tester.drag(find.byType(ListView), const Offset(0.0, -1500.0));
      await tester.pumpAndSettle();

      // Verify Provider Catalog sections are present on both nodes
      expect(find.textContaining('Provider Catalog'), findsNWidgets(2));

      // Verify Provider Snapshot display for node-1 (ollama provider)
      expect(find.text('provider-ollama'), findsOneWidget);
      expect(
        find.textContaining('llm / inference'),
        findsNWidgets(2),
      ); // both providers on node-1
      expect(
        find.textContaining('HEALTHY'),
        findsNWidgets(2),
      ); // both ollama and vllm are healthy
      expect(find.textContaining('Load: 3/10'), findsOneWidget);
      expect(find.textContaining('Q: 1'), findsOneWidget);
      expect(find.textContaining('30.0%'), findsOneWidget);
      expect(find.textContaining('Models: llama-3.1, mistral'), findsOneWidget);

      // Verify Provider Snapshot display for node-2 (degraded state)
      expect(find.text('provider-openai'), findsOneWidget);
      expect(find.textContaining('Load: 50/100'), findsOneWidget);
      expect(find.textContaining('Q: 5'), findsOneWidget);
      expect(find.textContaining('50.0%'), findsOneWidget);
    },
  );

  testWidgets(
    'Client App opens Execution/Logs panel and displays lifecycle events',
    (WidgetTester tester) async {
      final fakeClient = FakeClientWireClient(shouldSuccess: true);
      final fakeStatusRepo = FakeControlPlaneStatusRepository();

      await tester.pumpWidget(
        IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo),
      );
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 100));

      await tester.tap(find.byTooltip('Execution & Logs'));
      await tester.pump();

      // Verify logs header
      expect(find.text('Lifecycle Events & Logs'), findsOneWidget);

      // Verify captured events
      expect(find.text('ONLINE'), findsOneWidget);
      expect(find.text('WARN'), findsOneWidget);
      expect(
        find.text('Node connected and registered successfully'),
        findsOneWidget,
      );
      expect(find.text('Execution concurrency limit reached'), findsOneWidget);
    },
  );

  testWidgets('Client App refresh behavior when selected edge disappears', (
    WidgetTester tester,
  ) async {
    final fakeClient = FakeClientWireClient(shouldSuccess: true);
    final fakeStatusRepo = FakeControlPlaneStatusRepository();

    await tester.pumpWidget(
      IopClientApp(testClient: fakeClient, statusRepository: fakeStatusRepo),
    );
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    // 1. Initial State: edge-a is selected, EdgesPanel has Edge Alpha and Edge Beta
    await tester.tap(find.byTooltip('Edges'));
    await tester.pump();
    expect(find.text('Edge Alpha'), findsNWidgets(2)); // list & details

    // 2. Change mockEdges so 'edge-a' and 'edge-b' disappear, only 'edge-c' is returned.
    fakeStatusRepo.mockEdges = [
      EdgeRegistryView(
        edgeId: 'edge-c',
        edgeName: 'Edge Gamma',
        version: '1.2.2',
        capabilities: ['Automation'],
        protocol: 'iop-wire-v1',
        connected: true,
        lastSeen: DateTime.now(),
      ),
    ];

    // Trigger refresh in overview
    await tester.tap(find.byTooltip('Overview'));
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    // Verify route has navigated back to Overview successfully
    expect(find.text('Operations Overview'), findsOneWidget);

    // Drag the ListView upward to scroll down and mount the bottom refresh button
    final listViewFinder = find.byType(ListView);
    await tester.drag(listViewFinder.first, const Offset(0.0, -400.0));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 200));

    // Tap the refresh button safely by its text label
    await tester.tap(find.text('Refresh Connection'));
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    // 3. Switch to Edges panel, verify edge-a disappears, edge-c appears and auto-selected
    await tester.tap(find.byTooltip('Edges'));
    await tester.pump();

    expect(find.text('Edge Alpha'), findsNothing);
    expect(
      find.text('Edge Gamma'),
      findsNWidgets(2),
    ); // list & details (auto-selected)

    // 4. Switch to Nodes panel, verify dropdown Value works without assert crash
    await tester.tap(find.byTooltip('Nodes'));
    await tester.pump();
    expect(find.text('Nodes View'), findsOneWidget);
    // dropdown button value should be edge-c now
    expect(find.text('Edge Gamma'), findsOneWidget);

    // 5. Switch to Logs panel, verify dropdown Value works without assert crash
    await tester.tap(find.byTooltip('Execution & Logs'));
    await tester.pump();
    expect(find.text('Lifecycle Events & Logs'), findsOneWidget);
    expect(find.text('Edge Gamma'), findsOneWidget);
  });

  edgesPanelStatePreservationTests();
  nodesPanelStatePreservationTests();
}

Widget _wrapPanel(Widget child) {
  return MaterialApp(home: Scaffold(body: child));
}

FleetEdgeView _stateTestEdge() {
  return FleetEdgeView(
    edgeId: 'edge-a',
    edgeName: 'Edge Alpha',
    version: '1.0.0',
    protocol: 'iop-wire-v1',
    connected: true,
    health: 'Serving',
    lastSeen: DateTime(2026, 1, 1),
    nodeCount: 0,
    capabilities: const [],
    error: '',
  );
}

// Directly exercises the loading/error/empty branches that the split-out
// state widgets must preserve.
void edgesPanelStatePreservationTests() {
  testWidgets('EdgesPanel preserves loading error and empty states', (
    WidgetTester tester,
  ) async {
    await tester.pumpWidget(
      _wrapPanel(
        EdgesPanel(
          edges: const [],
          isLoading: true,
          error: null,
          selectedEdgeId: null,
          onSelectEdge: (_) {},
          onRefresh: () {},
        ),
      ),
    );
    expect(find.byType(CircularProgressIndicator), findsOneWidget);

    var retried = 0;
    await tester.pumpWidget(
      _wrapPanel(
        EdgesPanel(
          edges: const [],
          isLoading: false,
          error: 'boom edges',
          selectedEdgeId: null,
          onSelectEdge: (_) {},
          onRefresh: () => retried++,
        ),
      ),
    );
    expect(find.text('Failed to Load Edges'), findsOneWidget);
    expect(find.text('boom edges'), findsOneWidget);
    await tester.tap(find.text('Retry'));
    await tester.pump();
    expect(retried, 1);

    var refreshed = 0;
    await tester.pumpWidget(
      _wrapPanel(
        EdgesPanel(
          edges: const [],
          isLoading: false,
          error: null,
          selectedEdgeId: null,
          onSelectEdge: (_) {},
          onRefresh: () => refreshed++,
        ),
      ),
    );
    expect(find.text('No Edges Registered'), findsOneWidget);
    await tester.tap(find.text('Refresh'));
    await tester.pump();
    expect(refreshed, 1);
  });
}

// Directly exercises the loading/error/empty branches that the split-out
// state widgets must preserve.
void nodesPanelStatePreservationTests() {
  testWidgets('NodesPanel preserves loading error and empty states', (
    WidgetTester tester,
  ) async {
    final edges = [_stateTestEdge()];

    await tester.pumpWidget(
      _wrapPanel(
        NodesPanel(
          edges: edges,
          selectedEdgeId: 'edge-a',
          selectedEdgeStatus: null,
          isLoading: true,
          error: null,
          onSelectEdge: (_) {},
          onRefresh: () {},
        ),
      ),
    );
    expect(find.byType(CircularProgressIndicator), findsOneWidget);

    await tester.pumpWidget(
      _wrapPanel(
        NodesPanel(
          edges: edges,
          selectedEdgeId: 'edge-a',
          selectedEdgeStatus: null,
          isLoading: false,
          error: 'boom nodes',
          onSelectEdge: (_) {},
          onRefresh: () {},
        ),
      ),
    );
    expect(find.text('Failed to Load Nodes Status'), findsOneWidget);
    expect(find.text('boom nodes'), findsOneWidget);

    await tester.pumpWidget(
      _wrapPanel(
        NodesPanel(
          edges: edges,
          selectedEdgeId: 'edge-a',
          selectedEdgeStatus: null,
          isLoading: false,
          error: null,
          onSelectEdge: (_) {},
          onRefresh: () {},
        ),
      ),
    );
    expect(find.text('No Nodes Registered'), findsOneWidget);
  });
}
