import 'dart:convert';

import 'package:flutter_test/flutter_test.dart';

import 'package:appsok/src/features/settings/jenkins_web_login_page.dart';
import 'package:appsok/src/features/settings/settings_page.dart'
    show JenkinsWebLoginResult;

void main() {
  group('parseCrumbResult', () {
    test('returns crumb for valid response', () {
      const json =
          '{"crumbRequestField":"Jenkins-Crumb","crumb":"abc123","_class":"hudson.security.csrf.DefaultCrumbIssuer"}';
      final result = parseCrumbResult(json);
      expect(result, isNotNull);
      expect(result!.crumbRequestField, 'Jenkins-Crumb');
      expect(result.crumb, 'abc123');
    });

    test('returns null when crumbRequestField is empty', () {
      const json = '{"crumbRequestField":"","crumb":"abc123"}';
      expect(parseCrumbResult(json), isNull);
    });

    test('returns null when crumb is empty', () {
      const json = '{"crumbRequestField":"Jenkins-Crumb","crumb":""}';
      expect(parseCrumbResult(json), isNull);
    });

    test('returns null for malformed JSON', () {
      expect(parseCrumbResult('not-json'), isNull);
    });

    test('returns null for empty string', () {
      expect(parseCrumbResult(''), isNull);
    });
  });

  group('parseTokenResult', () {
    test('returns token for valid response', () {
      const json =
          '{"status":"ok","data":{"tokenName":"AppSok macOS 1234","tokenUuid":"uuid-1","tokenValue":"mytoken99"}}';
      final result = parseTokenResult(json);
      expect(result, isNotNull);
      expect(result!.tokenValue, 'mytoken99');
    });

    test('returns null when status is not ok', () {
      const json =
          '{"status":"error","data":{"tokenName":"t","tokenUuid":"u","tokenValue":"v"}}';
      expect(parseTokenResult(json), isNull);
    });

    test('returns null when tokenValue is empty', () {
      const json =
          '{"status":"ok","data":{"tokenName":"t","tokenUuid":"u","tokenValue":""}}';
      expect(parseTokenResult(json), isNull);
    });

    test('returns null when data is missing', () {
      const json = '{"status":"ok"}';
      expect(parseTokenResult(json), isNull);
    });

    test('returns null for malformed JSON', () {
      expect(parseTokenResult('{bad json'), isNull);
    });
  });

  group('parseBridgeTokenResult', () {
    String makeComposite({
      String crumbField = 'Jenkins-Crumb',
      String crumb = 'crumbval',
      String status = 'ok',
      String tokenValue = 'tok123',
    }) {
      final crumbJson = jsonEncode({
        'crumbRequestField': crumbField,
        'crumb': crumb,
      });
      final tokenJson = jsonEncode({
        'status': status,
        'data': {
          'tokenName': 'AppSok macOS 0',
          'tokenUuid': 'uuid',
          'tokenValue': tokenValue,
        },
      });
      return jsonEncode({'crumb': crumbJson, 'token': tokenJson});
    }

    test('returns parsed crumb and token for valid composite JSON', () {
      final result = parseBridgeTokenResult(makeComposite());
      expect(result, isNotNull);
      expect(result!.crumb.crumbRequestField, 'Jenkins-Crumb');
      expect(result.crumb.crumb, 'crumbval');
      expect(result.token.tokenValue, 'tok123');
    });

    test('returns null when crumb part is invalid', () {
      final composite = jsonEncode({
        'crumb': jsonEncode({'crumbRequestField': '', 'crumb': ''}),
        'token': jsonEncode({
          'status': 'ok',
          'data': {
            'tokenName': 't',
            'tokenUuid': 'u',
            'tokenValue': 'tok',
          },
        }),
      });
      expect(parseBridgeTokenResult(composite), isNull);
    });

    test('returns null when token status is error', () {
      final result =
          parseBridgeTokenResult(makeComposite(status: 'error'));
      expect(result, isNull);
    });

    test('returns null for malformed outer JSON', () {
      expect(parseBridgeTokenResult('{bad'), isNull);
    });

    test('returns null for null literal string', () {
      expect(parseBridgeTokenResult('null'), isNull);
    });
  });

  group('buildTokenBridgeJs', () {
    test('contains crumb path in generated JS', () {
      final js = buildTokenBridgeJs(
        '/crumbIssuer/api/json',
        '/user/toki/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken',
        'AppSok macOS 0',
      );
      expect(js, contains('/crumbIssuer/api/json'));
    });

    test('contains token path in generated JS', () {
      final js = buildTokenBridgeJs(
        '/crumbIssuer/api/json',
        '/user/toki/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken',
        'AppSok macOS 0',
      );
      expect(
        js,
        contains(
          '/user/toki/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken',
        ),
      );
    });

    test('uses same-origin credentials in generated JS', () {
      final js = buildTokenBridgeJs(
        '/crumbIssuer/api/json',
        '/user/toki/generateNewToken',
        'AppSok macOS 0',
      );
      expect(js, contains("credentials: 'same-origin'"));
    });

    test('token name is placed inside URLSearchParams value not raw JS', () {
      // The token name goes into URLSearchParams({ newTokenName: ... }) as a
      // JSON-encoded string value, not as raw JS — no eval/injection risk.
      final js = buildTokenBridgeJs(
        '/crumbIssuer/api/json',
        '/user/toki/generateNewToken',
        'AppSok macOS 1234',
      );
      expect(js, contains('newTokenName'));
      expect(js, contains('AppSok macOS 1234'));
      // Token name is inside a quoted JSON string, not unquoted.
      expect(js, contains('"AppSok macOS 1234"'));
    });

    test('percent-encoded username round-trips in token path', () {
      final username = 'ada lovelace';
      final encoded = Uri.encodeComponent(username);
      final tokenPath =
          '/user/$encoded/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken';
      final js = buildTokenBridgeJs('/crumbIssuer/api/json', tokenPath, 'AppSok macOS 0');
      expect(js, contains('ada%20lovelace'));
    });
  });

  group('JenkinsWebLoginResult apiToken contract', () {
    test('JenkinsWebLoginResult carries apiToken from bridge result', () {
      // Verifies the single-pop contract: _issueToken embeds apiToken into
      // JenkinsWebLoginResult via a single Navigator.pop.
      const result = JenkinsWebLoginResult(
        username: 'toki',
        displayName: 'Toki Lab',
        apiToken: 'issued-token-xyz',
      );
      expect(result.apiToken, 'issued-token-xyz');
      expect(result.username, 'toki');
    });

    test('JenkinsWebLoginResult apiToken defaults to null when not provided', () {
      const result = JenkinsWebLoginResult(username: 'toki');
      expect(result.apiToken, isNull);
    });

    test('parseBridgeTokenResult produces a tokenValue that maps to apiToken', () {
      // Simulates the bridge result → JenkinsWebLoginResult.apiToken path.
      final composite = jsonEncode({
        'crumb': jsonEncode({
          'crumbRequestField': 'Jenkins-Crumb',
          'crumb': 'crumbval',
        }),
        'token': jsonEncode({
          'status': 'ok',
          'data': {
            'tokenName': 'AppSok macOS 0',
            'tokenUuid': 'uuid',
            'tokenValue': 'bridge-token-abc',
          },
        }),
      });
      final bridgeResult = parseBridgeTokenResult(composite);
      expect(bridgeResult, isNotNull);
      final webResult = JenkinsWebLoginResult(
        username: 'toki',
        apiToken: bridgeResult!.token.tokenValue,
      );
      expect(webResult.apiToken, 'bridge-token-abc');
    });
  });

  group('jenkinsUri (token path construction)', () {
    test('encodes username with space for token generate path', () {
      final uri = jenkinsUri(
        'https://jenkins.example.com',
        'user/${Uri.encodeComponent("ada lovelace")}/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken',
      );
      expect(uri.path, contains('ada%20lovelace'));
    });

    test('context path URL produces correct token path', () {
      final uri = jenkinsUri(
        'https://jenkins.example.com/jenkins',
        'user/toki/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken',
      );
      expect(
        uri.path,
        '/jenkins/user/toki/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken',
      );
    });
  });
}
