import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';

import 'package:appsok/src/app.dart';
import 'package:appsok/src/features/settings/settings_page.dart';
import 'package:appsok/src/services/jenkins_client.dart';
import 'package:appsok/src/services/token_store.dart';
import 'package:appsok/src/theme/app_theme.dart';

void main() {
  setUp(() {
    FlutterSecureStorage.setMockInitialValues({});
  });

  // ── AppSokApp pre-stored session restore ─────────────────────────

  testWidgets(
    'AppSokApp activates jobs loader when session is stored before build',
    (tester) async {
      final requestedPaths = <String>[];

      FlutterSecureStorage.setMockInitialValues({
        'jenkins.baseUrl': 'https://jenkins.example',
        'jenkins.username': 'toki',
        'jenkins.apiToken': 'generated-api-token',
      });

      await tester.pumpWidget(
        AppSokApp(
          jenkinsClient: JenkinsClient(
            client: MockClient((request) async {
              if (request.url.path.endsWith('whoAmI/api/json')) {
                requestedPaths.add('whoAmI');
                return http.Response(jsonEncode({'id': 'toki'}), 200);
              }
              if (request.url.path.endsWith('/api/json')) {
                requestedPaths.add('jobs');
                return http.Response(
                  jsonEncode({
                    'jobs': [
                      {
                        'name': 'post-login-job',
                        'fullName': 'post-login-job',
                        'url': 'https://jenkins.example/job/post-login-job/',
                      },
                    ],
                  }),
                  200,
                );
              }
              return http.Response(jsonEncode({}), 200);
            }),
          ),
        ),
      );
      await tester.pumpAndSettle();

      expect(find.text('Jenkins 연결 필요'), findsNothing);
      expect(find.text('post-login-job'), findsOneWidget);
      expect(requestedPaths, containsAllInOrder(<String>['whoAmI', 'jobs']));
    },
  );

  // ── AppSokApp._saveToken + restore success path ──────────────────

  testWidgets(
    'AppSokApp: web login with apiToken saves session, validates, activates jobs loader',
    (tester) async {
      // No stored session at startup.
      FlutterSecureStorage.setMockInitialValues({});

      final requestedPaths = <String>[];

      await tester.pumpWidget(
        AppSokApp(
          jenkinsClient: JenkinsClient(
            client: MockClient((request) async {
              requestedPaths.add(request.url.path);
              if (request.url.path.endsWith('whoAmI/api/json')) {
                return http.Response(jsonEncode({'id': 'toki'}), 200);
              }
              if (request.url.path.endsWith('/api/json')) {
                return http.Response(
                  jsonEncode({
                    'jobs': [
                      {
                        'name': 'web-login-job',
                        'fullName': 'web-login-job',
                        'url': 'https://jenkins.example/job/web-login-job/',
                      },
                    ],
                  }),
                  200,
                );
              }
              return http.Response(jsonEncode({}), 200);
            }),
          ),
          // launcher returns apiToken → triggers real _saveToken path
          webLoginLauncher: (_) async => const JenkinsWebLoginResult(
            username: 'toki',
            displayName: 'Toki Lab',
            apiToken: 'issued-token-abc',
          ),
        ),
      );
      await tester.pumpAndSettle();

      // No session yet — jobs loader not active.
      expect(find.text('Jenkins 연결 필요'), findsOneWidget);

      // Navigate to Settings.
      await tester.tap(
        find.descendant(
          of: find.byType(NavigationRail),
          matching: find.text('설정'),
        ),
      );
      await tester.pumpAndSettle();

      await tester.enterText(
        find.byType(TextField).first,
        'https://jenkins.example',
      );
      await tester.pump();

      // Tap Web Login — triggers real _saveToken + restore path.
      await tester.tap(find.byKey(const ValueKey('jenkins-web-login-button')));
      await tester.pumpAndSettle();

      // _saveToken saved and validated: token-saved-label must appear.
      expect(find.byKey(const ValueKey('token-saved-label')), findsOneWidget);

      // whoAmI was called (validate on save) and again (restore for _startup).
      expect(
        requestedPaths.where((p) => p.endsWith('whoAmI/api/json')).length,
        greaterThanOrEqualTo(1),
      );

      // Navigate back to 빌드 tab to verify jobs loader is now active.
      await tester.tap(
        find.descendant(
          of: find.byType(NavigationRail),
          matching: find.text('빌드'),
        ),
      );
      await tester.pumpAndSettle();

      expect(find.text('Jenkins 연결 필요'), findsNothing);
      expect(find.text('web-login-job'), findsOneWidget);
    },
  );

  // ── AppSokApp._saveToken + restore failure path ──────────────────

  testWidgets(
    'AppSokApp: web login with apiToken clears session and shows error when restore fails',
    (tester) async {
      FlutterSecureStorage.setMockInitialValues({});

      await tester.pumpWidget(
        AppSokApp(
          jenkinsClient: JenkinsClient(
            client: MockClient((request) async {
              // All requests fail with 401.
              return http.Response('Unauthorized', 401);
            }),
          ),
          webLoginLauncher: (_) async => const JenkinsWebLoginResult(
            username: 'toki',
            displayName: 'Toki Lab',
            apiToken: 'bad-token',
          ),
        ),
      );
      await tester.pumpAndSettle();

      // Navigate to Settings.
      await tester.tap(
        find.descendant(
          of: find.byType(NavigationRail),
          matching: find.text('설정'),
        ),
      );
      await tester.pumpAndSettle();

      await tester.enterText(
        find.byType(TextField).first,
        'https://jenkins.example',
      );
      await tester.pump();

      await tester.tap(find.byKey(const ValueKey('jenkins-web-login-button')));
      await tester.pumpAndSettle();

      // Save failed: error UI shown, success label absent.
      expect(find.byKey(const ValueKey('token-save-error')), findsOneWidget);
      expect(find.byKey(const ValueKey('token-saved-label')), findsNothing);

      // Session must have been cleared — verify indirectly via jobs loader state.
      await tester.tap(
        find.descendant(
          of: find.byType(NavigationRail),
          matching: find.text('빌드'),
        ),
      );
      await tester.pumpAndSettle();
      expect(find.text('Jenkins 연결 필요'), findsOneWidget);
    },
  );

  testWidgets(
    'AppSokApp: clearing credential removes stored session and disables jobs loader',
    (tester) async {
      final requestedPaths = <String>[];

      FlutterSecureStorage.setMockInitialValues({
        'jenkins.baseUrl': 'https://jenkins.example',
        'jenkins.username': 'toki',
        'jenkins.apiToken': 'generated-api-token',
      });

      await tester.pumpWidget(
        AppSokApp(
          jenkinsClient: JenkinsClient(
            client: MockClient((request) async {
              requestedPaths.add(request.url.path);
              if (request.url.path.endsWith('whoAmI/api/json')) {
                return http.Response(jsonEncode({'id': 'toki'}), 200);
              }
              if (request.url.path.endsWith('/api/json')) {
                return http.Response(
                  jsonEncode({
                    'jobs': [
                      {
                        'name': 'stored-job',
                        'fullName': 'stored-job',
                        'url': 'https://jenkins.example/job/stored-job/',
                      },
                    ],
                  }),
                  200,
                );
              }
              return http.Response(jsonEncode({}), 200);
            }),
          ),
        ),
      );
      await tester.pumpAndSettle();

      expect(find.text('stored-job'), findsOneWidget);
      expect(requestedPaths, containsAllInOrder(<String>['/whoAmI/api/json']));

      await tester.tap(
        find.descendant(
          of: find.byType(NavigationRail),
          matching: find.text('설정'),
        ),
      );
      await tester.pumpAndSettle();

      await tester.tap(
        find.byKey(const ValueKey('jenkins-clear-session-button')),
      );
      await tester.pumpAndSettle();

      expect(
        find.byKey(const ValueKey('session-cleared-label')),
        findsOneWidget,
      );
      expect(await TokenStore().readSession(), isNull);

      await tester.tap(
        find.descendant(
          of: find.byType(NavigationRail),
          matching: find.text('빌드'),
        ),
      );
      await tester.pumpAndSettle();

      expect(find.text('Jenkins 연결 필요'), findsOneWidget);
      expect(find.text('stored-job'), findsNothing);
    },
  );

  // ── SettingsPage: apiToken → tokenSaver → onSessionSaved ────────

  testWidgets(
    'SettingsPage calls tokenSaver and onSessionSaved when launcher returns apiToken',
    (tester) async {
      var onSessionSavedCalled = false;
      String? savedUsername;
      String? savedToken;

      await tester.pumpWidget(
        MaterialApp(
          theme: AppTheme.light(),
          home: Scaffold(
            body: SettingsPage(
              tokenSaver: (baseUrl, username, apiToken) async {
                savedUsername = username;
                savedToken = apiToken;
                return true;
              },
              onSessionSaved: () => onSessionSavedCalled = true,
              webLoginLauncher: (_) async => const JenkinsWebLoginResult(
                username: 'toki',
                displayName: 'Toki Lab',
                apiToken: 'issued-token-abc',
              ),
            ),
          ),
        ),
      );

      await tester.enterText(
        find.byType(TextField).first,
        'https://jenkins.example.com',
      );
      await tester.pump();
      await tester.tap(find.byKey(const ValueKey('jenkins-web-login-button')));
      await tester.pumpAndSettle();

      expect(savedUsername, 'toki');
      expect(savedToken, 'issued-token-abc');
      expect(onSessionSavedCalled, isTrue);
      expect(find.byKey(const ValueKey('token-saved-label')), findsOneWidget);
      expect(tester.takeException(), isNull);
    },
  );

  testWidgets(
    'SettingsPage shows token-save-error when tokenSaver returns false',
    (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          theme: AppTheme.light(),
          home: Scaffold(
            body: SettingsPage(
              tokenSaver: (baseUrl, username, apiToken) async => false,
              onSessionSaved: () {},
              webLoginLauncher: (_) async => const JenkinsWebLoginResult(
                username: 'toki',
                displayName: 'Toki Lab',
                apiToken: 'bad-token',
              ),
            ),
          ),
        ),
      );

      await tester.enterText(
        find.byType(TextField).first,
        'https://jenkins.example.com',
      );
      await tester.pump();
      await tester.tap(find.byKey(const ValueKey('jenkins-web-login-button')));
      await tester.pumpAndSettle();

      expect(find.byKey(const ValueKey('token-save-error')), findsOneWidget);
      expect(find.byKey(const ValueKey('token-saved-label')), findsNothing);
      expect(tester.takeException(), isNull);
    },
  );

  testWidgets(
    'SettingsPage shows error when launcher returns result without apiToken',
    (tester) async {
      var tokenSaverCalled = false;

      await tester.pumpWidget(
        MaterialApp(
          theme: AppTheme.light(),
          home: Scaffold(
            body: SettingsPage(
              tokenSaver: (baseUrl, username, apiToken) async {
                tokenSaverCalled = true;
                return true;
              },
              onSessionSaved: () {},
              webLoginLauncher: (_) async => const JenkinsWebLoginResult(
                username: 'toki',
                displayName: 'Toki Lab',
              ),
            ),
          ),
        ),
      );

      await tester.enterText(
        find.byType(TextField).first,
        'https://jenkins.example.com',
      );
      await tester.pump();
      await tester.tap(find.byKey(const ValueKey('jenkins-web-login-button')));
      await tester.pumpAndSettle();

      expect(tokenSaverCalled, isFalse);
      expect(find.byKey(const ValueKey('web-login-result')), findsNothing);
      expect(find.textContaining('API token 발급에 실패했습니다'), findsOneWidget);
      expect(tester.takeException(), isNull);
    },
  );

  // ── reauth / network failure restore paths ───────────────────────

  testWidgets(
    'AppSokApp shows reauth required state when stored credential returns 401',
    (tester) async {
      FlutterSecureStorage.setMockInitialValues({
        'jenkins.baseUrl': 'https://jenkins.example',
        'jenkins.username': 'toki',
        'jenkins.apiToken': 'revoked-token',
      });

      await tester.pumpWidget(
        AppSokApp(
          jenkinsClient: JenkinsClient(
            client: MockClient(
              (request) async => http.Response('Unauthorized', 401),
            ),
          ),
        ),
      );
      await tester.pumpAndSettle();

      expect(find.text('재로그인 필요'), findsOneWidget);
      // Credential must be cleared after 401.
      expect(await TokenStore().readSession(), isNull);
    },
  );

  testWidgets(
    'AppSokApp shows reauth required state when stored credential returns 403',
    (tester) async {
      FlutterSecureStorage.setMockInitialValues({
        'jenkins.baseUrl': 'https://jenkins.example',
        'jenkins.username': 'toki',
        'jenkins.apiToken': 'forbidden-token',
      });

      await tester.pumpWidget(
        AppSokApp(
          jenkinsClient: JenkinsClient(
            client: MockClient(
              (request) async => http.Response('Forbidden', 403),
            ),
          ),
        ),
      );
      await tester.pumpAndSettle();

      expect(find.text('재로그인 필요'), findsOneWidget);
      expect(await TokenStore().readSession(), isNull);
    },
  );

  testWidgets(
    'AppSokApp shows network retry state when restore fails with IOException and preserves credential',
    (tester) async {
      FlutterSecureStorage.setMockInitialValues({
        'jenkins.baseUrl': 'https://jenkins.example',
        'jenkins.username': 'toki',
        'jenkins.apiToken': 'valid-token',
      });

      await tester.pumpWidget(
        AppSokApp(
          jenkinsClient: JenkinsClient(
            client: MockClient(
              (request) async =>
                  throw const SocketException('Network unreachable'),
            ),
          ),
        ),
      );
      await tester.pumpAndSettle();

      expect(find.text('Jenkins 연결 실패'), findsOneWidget);
      // Credential preserved on network failure.
      expect(await TokenStore().readSession(), isNotNull);
    },
  );

  testWidgets(
    'AppSokApp retry callback re-runs restore and activates jobs loader on success',
    (tester) async {
      FlutterSecureStorage.setMockInitialValues({
        'jenkins.baseUrl': 'https://jenkins.example',
        'jenkins.username': 'toki',
        'jenkins.apiToken': 'valid-token',
      });

      var callCount = 0;
      await tester.pumpWidget(
        AppSokApp(
          jenkinsClient: JenkinsClient(
            client: MockClient((request) async {
              callCount++;
              if (callCount == 1) {
                throw const SocketException('Network unreachable');
              }
              if (request.url.path.endsWith('whoAmI/api/json')) {
                return http.Response(jsonEncode({'id': 'toki'}), 200);
              }
              if (request.url.path.endsWith('/api/json')) {
                return http.Response(
                  jsonEncode({
                    'jobs': [
                      {
                        'name': 'retry-job',
                        'fullName': 'retry-job',
                        'url': 'https://jenkins.example/job/retry-job/',
                      },
                    ],
                  }),
                  200,
                );
              }
              return http.Response(jsonEncode({}), 200);
            }),
          ),
        ),
      );
      await tester.pumpAndSettle();

      expect(find.text('Jenkins 연결 실패'), findsOneWidget);

      await tester.tap(find.byKey(const ValueKey('job-status-action-button')));
      await tester.pumpAndSettle();

      expect(find.text('Jenkins 연결 실패'), findsNothing);
      expect(find.text('retry-job'), findsOneWidget);
    },
  );
}
