import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:oto_console/oto_console.dart';

void main() {
  const onlineConnection = OtoCoreConnectionSnapshot(
    state: OtoCoreConnectionState.online,
    health: OtoCoreEndpointStatus(
      label: 'Health',
      path: '/healthz',
      state: OtoCoreConnectionState.online,
      statusCode: 200,
      message: 'OK',
    ),
    readiness: OtoCoreEndpointStatus(
      label: 'Readiness',
      path: '/readyz',
      state: OtoCoreConnectionState.online,
      statusCode: 200,
      message: 'OK',
    ),
    policyNote: 'CORS headers or a same-origin proxy are required.',
  );

  test('exports console contract models without shell dependency', () {
    expect(
      OtoConsoleSection.values.map((section) => section.name),
      containsAllInOrder([
        'overview',
        'runners',
        'pipelines',
        'executions',
        'artifacts',
        'agent',
        'settings',
      ]),
    );

    final contractFile = File('lib/src/oto_console_contract.dart');
    final contractSource =
        (contractFile.existsSync()
                ? contractFile
                : File(
                    'packages/flutter/oto_console/lib/src/oto_console_contract.dart',
                  ))
            .readAsStringSync();
    expect(contractSource, isNot(contains('oto_console_shell.dart')));
  });

  testWidgets('renders embeddable OTO console shell', (tester) async {
    const config = OtoConsoleConfig(
      serverHttpUrl: 'http://localhost:8080',
      serverWireUrl: 'ws://localhost:18080/runner',
    );

    await tester.pumpWidget(
      const MaterialApp(
        home: OtoConsoleShell(
          config: config,
          capabilities: OtoCapabilityPack(
            capabilities: [
              OtoCapability(
                name: 'Remote Run',
                description: 'Dispatch remote build runs.',
              ),
            ],
          ),
          overview: OtoConsoleOverview(
            config: config,
            connection: onlineConnection,
          ),
        ),
      ),
    );

    expect(find.text('Build Operations Overview'), findsOneWidget);
    expect(find.text('CORE ONLINE'), findsOneWidget);
    expect(find.text('Health'), findsOneWidget);
    expect(find.text('Readiness'), findsOneWidget);
    expect(find.byTooltip('Agent'), findsOneWidget);

    // Jenkins IA shell: breadcrumb and Dashboard-level left menu labels.
    expect(find.text('OTO'), findsOneWidget);
    expect(find.text('New Item'), findsOneWidget);
    expect(find.text('Pipelines'), findsOneWidget);
    expect(find.text('Build History'), findsOneWidget);

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

    expect(find.textContaining('OTO agent surface is ready'), findsOneWidget);
    expect(find.textContaining('Remote Run'), findsOneWidget);
  });

  testWidgets('New Item opens create intent without selecting Pipelines', (
    tester,
  ) async {
    const config = OtoConsoleConfig(
      serverHttpUrl: 'http://localhost:8080',
      serverWireUrl: 'ws://localhost:18080/runner',
    );
    final navigations = <OtoConsoleSection>[];
    var newItemTaps = 0;
    var showCreate = false;

    await tester.pumpWidget(
      StatefulBuilder(
        builder: (context, setHostState) => MaterialApp(
          home: OtoConsoleShell(
            config: config,
            overview: const OtoConsoleOverview(
              config: config,
              connection: onlineConnection,
            ),
            pipelines: OtoJobsSurface(
              snapshot: const OtoSurfaceSnapshot.empty(),
              jobCreateState: const OtoActionViewState.idle(),
              showCreateForm: showCreate,
              createOnly: showCreate,
              onCreateJob: (_) {},
            ),
            onNavigate: (section) {
              navigations.add(section);
              setHostState(() {
                showCreate = false;
              });
            },
            onNewItem: () {
              newItemTaps++;
              setHostState(() {
                showCreate = true;
              });
            },
          ),
        ),
      ),
    );

    await tester.tap(find.text('New Item'));
    await tester.pumpAndSettle();

    expect(navigations, [OtoConsoleSection.pipelines]);
    expect(newItemTaps, 1);
    expect(find.text('New Item section'), findsOneWidget);
    expect(find.text('Pipelines / Jobs'), findsNothing);
    expect(find.text('No pipeline jobs'), findsNothing);
    expect(find.byType(TextField), findsNWidgets(2));

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

    expect(find.text('Pipelines section'), findsOneWidget);
    expect(find.text('Pipelines / Jobs'), findsOneWidget);
    expect(find.text('No pipeline jobs'), findsOneWidget);
    expect(find.byType(TextField), findsNothing);
  });

  testWidgets('renders section surfaces with empty states', (tester) async {
    const config = OtoConsoleConfig(
      serverHttpUrl: 'http://localhost:8080',
      serverWireUrl: 'ws://localhost:18080/runner',
    );

    await tester.pumpWidget(
      const MaterialApp(
        home: OtoConsoleShell(
          config: config,
          overview: OtoConsoleOverview(
            config: config,
            connection: onlineConnection,
          ),
        ),
      ),
    );

    final sections = <String, String>{
      'Runners': 'No runners connected',
      'Pipelines': 'No pipeline jobs',
      'Executions': 'No executions',
      'Artifacts': 'No artifacts',
    };

    for (final entry in sections.entries) {
      await tester.tap(find.byTooltip(entry.key));
      await tester.pumpAndSettle();

      expect(find.text(entry.value), findsOneWidget);
    }

    // Settings is no longer a generic empty-state fallback: it renders the
    // Manage OTO category surface (System, Security -> Browser policy).
    await tester.tap(find.byTooltip('Settings'));
    await tester.pumpAndSettle();

    expect(find.text('Manage OTO'), findsOneWidget);
    expect(find.text('System'), findsOneWidget);
    expect(find.text('Security'), findsOneWidget);
    expect(find.text('Browser policy'), findsOneWidget);
    expect(find.text('Browser API policy'), findsOneWidget);
    expect(find.textContaining('same-origin proxy'), findsOneWidget);
  });

  testWidgets('OtoRunnersSurface renders loading state', (tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: OtoRunnersSurface(snapshot: OtoSurfaceSnapshot.loading()),
        ),
      ),
    );

    expect(find.byType(CircularProgressIndicator), findsOneWidget);
  });

  testWidgets('OtoRunnersSurface renders empty state', (tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: OtoRunnersSurface(snapshot: OtoSurfaceSnapshot.empty()),
        ),
      ),
    );

    expect(find.text('No runners connected'), findsOneWidget);
    expect(find.text('Runner registry is empty.'), findsOneWidget);
  });

  testWidgets('OtoRunnersSurface renders error state', (tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: OtoRunnersSurface(
            snapshot: OtoSurfaceSnapshot.error('Failed to fetch runners'),
          ),
        ),
      ),
    );

    expect(find.text('Error loading runners'), findsOneWidget);
    expect(find.text('Failed to fetch runners'), findsOneWidget);
  });

  testWidgets('OtoRunnersSurface renders data state with runners list', (
    tester,
  ) async {
    final runners = [
      const OtoRunnerViewModel(
        runnerID: 'runner-1',
        alias: 'Main Runner',
        status: 'active',
        currentJobID: 'job-101',
        currentExecutionID: 'exec-202',
        message: 'Running jobs',
      ),
      const OtoRunnerViewModel(
        runnerID: 'runner-2',
        alias: 'Backup Runner',
        status: 'offline',
        currentJobID: '',
        currentExecutionID: '',
        message: 'Connection lost',
      ),
    ];

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoRunnersSurface(snapshot: OtoSurfaceSnapshot.data(runners)),
        ),
      ),
    );

    // Jenkins Nodes/Agents-style table header.
    expect(find.text('Nodes'), findsOneWidget);
    expect(find.text('Executor'), findsOneWidget);
    expect(find.text('Current job'), findsOneWidget);
    expect(find.text('Current run'), findsOneWidget);
    expect(find.text('Manage'), findsOneWidget);

    expect(find.text('ID: runner-1'), findsOneWidget);
    expect(find.text('Alias: Main Runner'), findsOneWidget);
    expect(find.text('ACTIVE'), findsOneWidget);
    expect(find.text('Message: Running jobs'), findsOneWidget);
    expect(find.textContaining('Job: job-101'), findsOneWidget);
    expect(find.textContaining('Execution: exec-202'), findsOneWidget);

    expect(find.text('ID: runner-2'), findsOneWidget);
    expect(find.text('Alias: Backup Runner'), findsOneWidget);
    expect(find.text('OFFLINE'), findsOneWidget);
    expect(find.text('Message: Connection lost'), findsOneWidget);
  });

  testWidgets('OtoRunnersSurface renders runner action states', (tester) async {
    final runners = [
      const OtoRunnerViewModel(
        runnerID: 'runner-1',
        alias: 'Test Runner',
        status: 'idle',
        currentJobID: '',
        currentExecutionID: '',
        message: '',
      ),
    ];

    OtoRunnerSelfUpdateDraft? capturedDraft;

    // --- Idle state: form visible, submit disabled (empty fields) ---
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoRunnersSurface(
            snapshot: OtoSurfaceSnapshot.data(runners),
            onSelfUpdateRunner: (draft) {
              capturedDraft = draft;
            },
          ),
        ),
      ),
    );
    expect(find.text('Self Update'), findsOneWidget);
    expect(find.text('Update'), findsOneWidget);
    final disabledBtn = tester.widget<ElevatedButton>(
      find.ancestor(
        of: find.text('Update'),
        matching: find.byType(ElevatedButton),
      ),
    );
    expect(disabledBtn.onPressed, isNull);

    // --- HTTPS URL validation: non-HTTPS keeps button disabled ---
    await tester.enterText(find.byType(TextField).at(0), 'v1.2.3');
    await tester.pump();
    await tester.enterText(
      find.byType(TextField).at(1),
      'http://not-https.example.com/oto',
    );
    await tester.pump();
    final httpBtn = tester.widget<ElevatedButton>(
      find.ancestor(
        of: find.text('Update'),
        matching: find.byType(ElevatedButton),
      ),
    );
    expect(httpBtn.onPressed, isNull);

    // --- HTTPS URL: button enabled ---
    await tester.enterText(
      find.byType(TextField).at(1),
      'https://releases.example.test/oto-agent',
    );
    await tester.pump();
    final enabledBtn = tester.widget<ElevatedButton>(
      find.ancestor(
        of: find.text('Update'),
        matching: find.byType(ElevatedButton),
      ),
    );
    expect(enabledBtn.onPressed, isNotNull);

    // --- Tap submit: callback receives correct draft ---
    await tester.tap(find.text('Update'));
    await tester.pump();
    expect(capturedDraft, isNotNull);
    expect(capturedDraft!.runnerID, 'runner-1');
    expect(capturedDraft!.version, 'v1.2.3');
    expect(
      capturedDraft!.downloadUrl,
      'https://releases.example.test/oto-agent',
    );

    // --- Submitting state: spinner shown, button disabled ---
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoRunnersSurface(
            snapshot: OtoSurfaceSnapshot.data(runners),
            actionStates: {'runner-1': const OtoActionViewState.submitting()},
            onSelfUpdateRunner: (draft) {},
          ),
        ),
      ),
    );
    expect(find.byType(CircularProgressIndicator), findsAtLeast(1));
    final submittingBtn = tester.widget<ElevatedButton>(
      find.ancestor(
        of: find.byType(CircularProgressIndicator).first,
        matching: find.byType(ElevatedButton),
      ),
    );
    expect(submittingBtn.onPressed, isNull);

    // --- Deferred state: deferred message shown ---
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoRunnersSurface(
            snapshot: OtoSurfaceSnapshot.data(runners),
            actionStates: {
              'runner-1': const OtoActionViewState.deferred(
                message: 'Update scheduled',
              ),
            },
            onSelfUpdateRunner: (draft) {},
          ),
        ),
      ),
    );
    expect(find.textContaining('Deferred: Update scheduled'), findsOneWidget);

    // --- Error state: error message shown ---
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoRunnersSurface(
            snapshot: OtoSurfaceSnapshot.data(runners),
            actionStates: {
              'runner-1': const OtoActionViewState.failed(
                message: 'Runner not found',
              ),
            },
            onSelfUpdateRunner: (draft) {},
          ),
        ),
      ),
    );
    expect(find.textContaining('Runner not found'), findsOneWidget);
  });

  testWidgets('OtoJobsSurface renders loading/empty/error/data states', (
    tester,
  ) async {
    // Loading
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(snapshot: OtoSurfaceSnapshot.loading()),
        ),
      ),
    );
    expect(find.byType(CircularProgressIndicator), findsOneWidget);

    // Empty
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(snapshot: OtoSurfaceSnapshot.empty()),
        ),
      ),
    );
    expect(find.text('No pipeline jobs'), findsOneWidget);
    expect(find.text('Job queue is empty.'), findsOneWidget);

    // Error
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(
            snapshot: OtoSurfaceSnapshot.error('Failed to fetch jobs'),
          ),
        ),
      ),
    );
    expect(find.text('Error loading jobs'), findsOneWidget);
    expect(find.text('Failed to fetch jobs'), findsOneWidget);

    // Data
    final jobs = [
      const OtoJobViewModel(
        jobID: 'job-1',
        name: 'Build Job',
        state: 'running',
        executionID: 'exec-1',
        createdAt: '2026-06-15T00:00:00Z',
        updatedAt: '2026-06-15T00:05:00Z',
      ),
    ];
    String? tappedExecution;
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(
            snapshot: OtoSurfaceSnapshot.data(jobs),
            onExecutionTap: (id) => tappedExecution = id,
          ),
        ),
      ),
    );
    expect(find.text('Build Job'), findsOneWidget);
    expect(find.text('RUNNING'), findsOneWidget);
    expect(find.text('Job ID: job-1'), findsOneWidget);
    expect(find.textContaining('Execution: exec-1'), findsOneWidget);

    // Jenkins dashboard job table: header columns and job-context task menu.
    expect(find.text('Name'), findsOneWidget);
    expect(find.text('Last Run'), findsOneWidget);
    expect(find.text('Updated'), findsOneWidget);
    expect(find.text('Actions'), findsOneWidget);
    // 'Status' appears as both a table header and a job-context task menu
    // chip; 'Build History' appears in the context strip and the task menu.
    expect(find.text('Status'), findsNWidgets(2));
    expect(find.text('Build History'), findsNWidgets(2));
    expect(find.text('Stages'), findsOneWidget);
    expect(find.text('Build Queue'), findsOneWidget);

    await tester.tap(find.textContaining('Execution: exec-1'));
    await tester.pumpAndSettle();
    expect(tappedExecution, 'exec-1');
  });

  testWidgets(
    'OtoExecutionsSurface renders loading/empty/error/data states and expanded detail',
    (tester) async {
      // Loading
      await tester.pumpWidget(
        const MaterialApp(
          home: Scaffold(
            body: OtoExecutionsSurface(snapshot: OtoSurfaceSnapshot.loading()),
          ),
        ),
      );
      expect(find.byType(CircularProgressIndicator), findsOneWidget);

      // Empty
      await tester.pumpWidget(
        const MaterialApp(
          home: Scaffold(
            body: OtoExecutionsSurface(snapshot: OtoSurfaceSnapshot.empty()),
          ),
        ),
      );
      expect(find.text('No executions'), findsOneWidget);
      expect(find.text('Execution history is empty.'), findsOneWidget);

      // Error
      await tester.pumpWidget(
        const MaterialApp(
          home: Scaffold(
            body: OtoExecutionsSurface(
              snapshot: OtoSurfaceSnapshot.error('Failed to fetch executions'),
            ),
          ),
        ),
      );
      expect(find.text('Error loading executions'), findsOneWidget);
      expect(find.text('Failed to fetch executions'), findsOneWidget);

      // Data
      final executions = [
        const OtoExecutionViewModel(
          executionID: 'exec-1',
          jobID: 'job-1',
          state: 'succeeded',
          runnerID: 'runner-1',
          createdAt: '2026-06-15T00:01:00Z',
          updatedAt: '2026-06-15T00:05:00Z',
        ),
      ];
      String? expandedId;
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: OtoExecutionsSurface(
              snapshot: OtoSurfaceSnapshot.data(executions),
              onExpandExecution: (id) => expandedId = id,
            ),
          ),
        ),
      );
      expect(find.text('Exec ID: exec-1'), findsOneWidget);
      expect(find.text('SUCCEEDED'), findsOneWidget);
      expect(find.text('Job ID: job-1'), findsOneWidget);
      expect(find.text('Runner ID: runner-1'), findsOneWidget);
      // No run selected yet: detail region shows the placeholder.
      expect(find.text('Select a run to view details.'), findsOneWidget);

      await tester.tap(find.text('Exec ID: exec-1'));
      await tester.pumpAndSettle();
      expect(expandedId, 'exec-1');

      // Expanded detail rendering
      final logs = [
        const OtoLogEntryViewModel(
          timestamp: '12:00:00',
          line: 'Starting build...',
        ),
        const OtoLogEntryViewModel(
          timestamp: '12:00:01',
          line: 'Finished successfully',
        ),
      ];
      final artifacts = [
        const OtoArtifactViewModel(
          name: 'binary.exe',
          path: '/build/binary.exe',
        ),
      ];
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: OtoExecutionsSurface(
              snapshot: OtoSurfaceSnapshot.data(executions),
              expandedExecutionID: 'exec-1',
              logsSnapshot: OtoSurfaceSnapshot.data(logs),
              artifactsSnapshot: OtoSurfaceSnapshot.data(artifacts),
            ),
          ),
        ),
      );
      // Selected run detail: run header + run-context task menu + stage
      // graph + console output/artifacts panes.
      expect(find.text('Run exec-1'), findsOneWidget);
      // 'Console Output', 'Pipeline Overview', and 'Artifacts' each appear
      // once in the run-context task menu and once as a detail pane title.
      expect(find.text('Console Output'), findsNWidgets(2));
      expect(find.text('Pipeline Overview'), findsNWidgets(2));
      expect(find.text('Build History'), findsOneWidget);
      expect(find.text('Stage Graph'), findsOneWidget);
      expect(find.text('Checkout'), findsOneWidget);
      expect(find.text('Build'), findsOneWidget);
      expect(find.text('Package'), findsOneWidget);
      expect(find.text('Publish'), findsOneWidget);
      expect(find.textContaining('Starting build...'), findsOneWidget);
      expect(find.text('Artifacts'), findsNWidgets(2));
      expect(find.text('binary.exe'), findsOneWidget);
    },
  );

  testWidgets('OtoArtifactsSurface renders loading/empty/error/data states', (
    tester,
  ) async {
    // Loading
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: OtoArtifactsSurface(snapshot: OtoSurfaceSnapshot.loading()),
        ),
      ),
    );
    expect(find.byType(CircularProgressIndicator), findsOneWidget);

    // Empty
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: OtoArtifactsSurface(snapshot: OtoSurfaceSnapshot.empty()),
        ),
      ),
    );
    expect(find.text('No artifacts'), findsOneWidget);
    expect(find.text('Artifact events are empty.'), findsOneWidget);

    // Error
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: OtoArtifactsSurface(
            snapshot: OtoSurfaceSnapshot.error('Failed to fetch artifacts'),
          ),
        ),
      ),
    );
    expect(find.text('Error loading artifacts'), findsOneWidget);
    expect(find.text('Failed to fetch artifacts'), findsOneWidget);

    // Data
    final artifacts = [
      const OtoArtifactViewModel(name: 'app.apk', path: '/build/app.apk'),
    ];
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoArtifactsSurface(
            snapshot: OtoSurfaceSnapshot.data(artifacts),
          ),
        ),
      ),
    );
    expect(find.text('app.apk'), findsOneWidget);
    expect(find.text('Path: /build/app.apk'), findsOneWidget);
  });

  testWidgets('OtoExecutionsSurface renders execution action states', (
    tester,
  ) async {
    final executionsWithRunner = [
      const OtoExecutionViewModel(
        executionID: 'exec-1',
        jobID: 'job-1',
        state: 'running',
        runnerID: 'runner-1',
        createdAt: '2026-06-15T00:01:00Z',
        updatedAt: '2026-06-15T00:05:00Z',
      ),
    ];

    String? capturedReportExecID;
    bool capturedReportSuccess = false;
    String? capturedLogExecID;
    String? capturedArtifactExecID;

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoExecutionsSurface(
            snapshot: OtoSurfaceSnapshot.data(executionsWithRunner),
            expandedExecutionID: 'exec-1',
            onReportExecution: (draft) {
              capturedReportExecID = draft.executionID;
              capturedReportSuccess = draft.success;
            },
            onAppendLog: (draft) {
              capturedLogExecID = draft.executionID;
            },
            onAppendArtifact: (draft) {
              capturedArtifactExecID = draft.executionID;
            },
          ),
        ),
      ),
    );

    expect(find.text('Actions'), findsOneWidget);
    expect(find.text('Cancel'), findsOneWidget);
    expect(find.text('Report'), findsOneWidget);
    expect(find.text('Log'), findsOneWidget);
    expect(find.text('Artifact'), findsOneWidget);

    // Test Report callback
    await tester.tap(find.text('Report'));
    await tester.pumpAndSettle();
    expect(capturedReportExecID, 'exec-1');
    expect(capturedReportSuccess, true);

    // Test Log callback
    capturedLogExecID = null;
    await tester.tap(find.text('Log'));
    await tester.pumpAndSettle();
    expect(capturedLogExecID, 'exec-1');

    // Test Artifact callback
    capturedArtifactExecID = null;
    await tester.tap(find.text('Artifact'));
    await tester.pumpAndSettle();
    expect(capturedArtifactExecID, 'exec-1');

    // --- Submitting state shows spinner ---
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoExecutionsSurface(
            snapshot: OtoSurfaceSnapshot.data(executionsWithRunner),
            expandedExecutionID: 'exec-1',
            actionStates: {'exec-1': const OtoActionViewState.submitting()},
            onReportExecution: (draft) {},
            onAppendLog: (draft) {},
            onAppendArtifact: (draft) {},
          ),
        ),
      ),
    );
    expect(find.byType(CircularProgressIndicator), findsAtLeast(1));

    // --- Success state shows feedback text ---
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoExecutionsSurface(
            snapshot: OtoSurfaceSnapshot.data(executionsWithRunner),
            expandedExecutionID: 'exec-1',
            actionStates: {
              'exec-1': const OtoActionViewState.succeeded(
                message: 'Report accepted',
              ),
            },
            onReportExecution: (draft) {},
            onAppendLog: (draft) {},
            onAppendArtifact: (draft) {},
          ),
        ),
      ),
    );
    expect(find.textContaining('Report accepted'), findsOneWidget);

    // --- Failed state shows error text ---
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoExecutionsSurface(
            snapshot: OtoSurfaceSnapshot.data(executionsWithRunner),
            expandedExecutionID: 'exec-1',
            actionStates: {
              'exec-1': const OtoActionViewState.failed(
                message: 'Cancel rejected',
              ),
            },
            onReportExecution: (draft) {},
            onAppendLog: (draft) {},
            onAppendArtifact: (draft) {},
          ),
        ),
      ),
    );
    expect(find.textContaining('Cancel rejected'), findsOneWidget);
  });

  testWidgets('OtoExecutionsSurface shows Actions section when expanded', (
    tester,
  ) async {
    final executionsWithRunner = [
      const OtoExecutionViewModel(
        executionID: 'exec-actions',
        jobID: 'job-1',
        state: 'running',
        runnerID: 'runner-1',
        createdAt: '2026-06-15T00:01:00Z',
        updatedAt: '2026-06-15T00:05:00Z',
      ),
    ];

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoExecutionsSurface(
            snapshot: OtoSurfaceSnapshot.data(executionsWithRunner),
            expandedExecutionID: 'exec-actions',
          ),
        ),
      ),
    );

    expect(find.text('Actions'), findsOneWidget);
    expect(find.byIcon(Icons.stop_circle), findsOneWidget);
    expect(find.byIcon(Icons.check_circle), findsOneWidget);
    expect(find.byIcon(Icons.bug_report), findsOneWidget);
    expect(find.byIcon(Icons.insert_drive_file), findsOneWidget);
  });

  testWidgets('OtoJobsSurface renders job create action states', (
    tester,
  ) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(
            snapshot: const OtoSurfaceSnapshot.empty(),
            jobCreateState: const OtoActionViewState.idle(),
            onCreateJob: (draft) {},
          ),
        ),
      ),
    );
    expect(find.textContaining('New Item'), findsOneWidget);
    expect(find.byType(TextField), findsNWidgets(2));
    final createButton = find.ancestor(
      of: find.text('Create'),
      matching: find.byType(ElevatedButton),
    );
    expect(createButton, findsOneWidget);
    final disabledButton = tester.widget<ElevatedButton>(createButton);
    expect(disabledButton.onPressed, isNull);

    await tester.enterText(find.byType(TextField).at(0), 'my-job-id');
    await tester.pump();
    var button = tester.widget<ElevatedButton>(
      find.ancestor(
        of: find.text('Create'),
        matching: find.byType(ElevatedButton),
      ),
    );
    expect(button.onPressed, isNull);

    await tester.enterText(find.byType(TextField).at(1), 'My Job Name');
    await tester.pump();
    button = tester.widget<ElevatedButton>(
      find.ancestor(
        of: find.text('Create'),
        matching: find.byType(ElevatedButton),
      ),
    );
    expect(button.onPressed, isNotNull);

    OtoJobCreateDraft? capturedDraft;
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(
            snapshot: const OtoSurfaceSnapshot.empty(),
            jobCreateState: const OtoActionViewState.idle(),
            onCreateJob: (draft) {
              capturedDraft = draft;
            },
          ),
        ),
      ),
    );
    await tester.enterText(find.byType(TextField).at(0), 'draft-id');
    await tester.pump();
    await tester.enterText(find.byType(TextField).at(1), 'Draft Name');
    await tester.pump();
    await tester.tap(find.text('Create'));
    await tester.pump();
    expect(capturedDraft, isNotNull);
    expect(capturedDraft!.id, 'draft-id');
    expect(capturedDraft!.name, 'Draft Name');

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(
            snapshot: const OtoSurfaceSnapshot.empty(),
            jobCreateState: const OtoActionViewState.submitting(),
            onCreateJob: (draft) {},
          ),
        ),
      ),
    );
    expect(find.byType(CircularProgressIndicator), findsOneWidget);
    expect(find.byType(ElevatedButton), findsOneWidget);

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(
            snapshot: const OtoSurfaceSnapshot.empty(),
            jobCreateState: const OtoActionViewState.succeeded(
              message: 'Job created: job-123',
            ),
            onCreateJob: (draft) {},
          ),
        ),
      ),
    );
    expect(find.textContaining('Job created'), findsOneWidget);
    expect(find.textContaining('job-123'), findsOneWidget);

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(
            snapshot: const OtoSurfaceSnapshot.empty(),
            jobCreateState: const OtoActionViewState.failed(
              message: 'Invalid request',
            ),
            onCreateJob: (draft) {},
          ),
        ),
      ),
    );
    expect(find.textContaining('Invalid request'), findsOneWidget);
  });

  testWidgets(
    'OtoJobsSurface hides job create form when create intent is off',
    (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: OtoJobsSurface(
              snapshot: const OtoSurfaceSnapshot.empty(),
              jobCreateState: const OtoActionViewState.idle(),
              showCreateForm: false,
              onCreateJob: (draft) {},
            ),
          ),
        ),
      );

      expect(find.byType(TextField), findsNothing);
      expect(find.text('Create'), findsNothing);
      expect(find.text('No pipeline jobs'), findsOneWidget);
    },
  );

  testWidgets('OtoJobsSurface renders a create-only New Item surface', (
    tester,
  ) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: OtoJobsSurface(
            snapshot: const OtoSurfaceSnapshot.empty(),
            jobCreateState: const OtoActionViewState.idle(),
            showCreateForm: true,
            createOnly: true,
            onCreateJob: (draft) {},
          ),
        ),
      ),
    );

    expect(find.text('Pipelines / Jobs'), findsNothing);
    expect(find.text('Build Queue'), findsNothing);
    expect(find.text('No pipeline jobs'), findsNothing);
    expect(find.byType(TextField), findsNWidgets(2));
    expect(find.text('Create'), findsOneWidget);
  });
}
