import 'dart:io';

import 'package:flutter_test/flutter_test.dart';

void main() {
  group('certified macOS build scripts', () {
    test('secret setup stores repeatable encrypted CI inputs', () {
      final script = _readScript('scripts/setup-appsok-ci-secrets.sh');

      expect(script, contains(r'age-keygen -o "$AGE_KEY"'));
      expect(
        script,
        contains(r'SOPS_PATH_REGEX="$(SECRET_FILE="$SECRET_FILE"'),
      );
      expect(
        script,
        contains(r'print(re.escape(os.environ["SECRET_FILE"]) + "$")'),
      );
      expect(script, contains(r'path_regex: $SOPS_PATH_REGEX'));
      expect(
        script,
        contains(r'sops --filename-override "$SECRET_FILE" -e "$tmp"'),
      );
      expect(script, contains(r'SOPS_AGE_KEY_FILE="$AGE_KEY" sops -d'));
      expect(script, contains('security unlock-keychain'));
      expect(script, contains('security set-key-partition-list'));
      expect(script, contains('xcrun notarytool store-credentials'));
      expect(script, contains('xcrun notarytool history --keychain-profile'));

      expect(script, contains('prompt_secret()'));
      expect(
        script,
        contains('prompt_secret "Mac login/keychain password" KEYCHAIN_PASSWORD'),
      );
      expect(
        script,
        contains('prompt_secret "App-specific password" APP_SPECIFIC_PASSWORD'),
      );
      expect(
        script,
        contains('prompt_secret "Jenkins API token" JENKINS_API_TOKEN'),
      );
      expect(
        script,
        isNot(
          contains('prompt_plain "Mac login/keychain password" KEYCHAIN_PASSWORD'),
        ),
      );
      expect(
        script,
        isNot(
          contains('prompt_plain "App-specific password" APP_SPECIFIC_PASSWORD'),
        ),
      );
      expect(
        script,
        isNot(contains('prompt_plain "Jenkins API token" JENKINS_API_TOKEN')),
      );

      expect(script, contains('prompt_plain "Jenkins URL" JENKINS_URL'));
      expect(
        script,
        contains('prompt_plain "Jenkins username" JENKINS_USERNAME'),
      );
      expect(script, contains('"jenkins_url": os.environ["JENKINS_URL"]'));
      expect(
        script,
        contains('"jenkins_username": os.environ["JENKINS_USERNAME"]'),
      );
      expect(
        script,
        contains('"jenkins_api_token": os.environ["JENKINS_API_TOKEN"]'),
      );
    });

    test('certified build keeps Flutter gates before release packaging', () {
      final script = _readScript('scripts/build-certified-macos.sh');

      _expectOrder(script, 'flutter pub get', 'flutter analyze');
      _expectOrder(script, 'flutter analyze', 'flutter test');
      _expectOrder(script, 'flutter test', 'flutter build macos');
      _expectOrder(script, 'flutter build macos', 'codesign --force');
    });

    test('certified build signs bundled adb before app notarization', () {
      final script = _readScript('scripts/build-certified-macos.sh');

      expect(
        script,
        contains(r'ADB_PATH="$APP_PATH/Contents/Resources/adb-runtime/adb"'),
      );
      expect(
        script,
        contains(
          r'codesign --force --options runtime --timestamp --sign "$IDENTITY" "$ADB_PATH"',
        ),
      );
      expect(
        script,
        contains(r'codesign --verify --strict --verbose=2 "$ADB_PATH"'),
      );
      _expectOrder(
        script,
        r'codesign --force --options runtime --timestamp --sign "$IDENTITY" "$ADB_PATH"',
        '--entitlements macos/Runner/Release.entitlements',
      );
    });

    test('certified build notarizes, staples, assesses, and zips artifact', () {
      final script = _readScript('scripts/build-certified-macos.sh');

      expect(
        script,
        contains(
          r'xcrun notarytool submit "$NOTARY_ZIP" --keychain-profile "$NOTARY_PROFILE" --wait',
        ),
      );
      expect(script, contains(r'xcrun stapler staple -v "$APP_PATH"'));
      expect(
        script,
        contains(r'spctl --assess --type execute --verbose=4 "$APP_PATH"'),
      );
      expect(
        script,
        contains(
          'FINAL_ZIP="build/macos/Build/Products/Release/AppSok-certified.zip"',
        ),
      );
      expect(script, contains(r'FINAL_SHA256="$FINAL_ZIP.sha256"'));
      expect(
        script,
        contains(r'shasum -a 256 "$FINAL_ZIP" | tee "$FINAL_SHA256"'),
      );
      expect(script, contains(r'ls -lh "$FINAL_SHA256"'));

      _expectOrder(
        script,
        r'xcrun notarytool submit "$NOTARY_ZIP" --keychain-profile "$NOTARY_PROFILE" --wait',
        r'xcrun stapler staple -v "$APP_PATH"',
      );
      _expectOrder(
        script,
        r'xcrun stapler staple -v "$APP_PATH"',
        r'spctl --assess --type execute --verbose=4 "$APP_PATH"',
      );
      _expectOrder(
        script,
        r'spctl --assess --type execute --verbose=4 "$APP_PATH"',
        r'ditto -c -k --keepParent "$APP_PATH" "$FINAL_ZIP"',
      );
      _expectOrder(
        script,
        r'ditto -c -k --keepParent "$APP_PATH" "$FINAL_ZIP"',
        r'shasum -a 256 "$FINAL_ZIP" | tee "$FINAL_SHA256"',
      );
    });

    test('jenkins job upsert configures thin certified job', () {
      final script = _readScript('scripts/upsert-jenkins-certified-job.sh');

      expect(script, contains('appsok-macos-certified'));
      expect(script, contains('macbook-ultra'));
      expect(script, contains('*/main'));
      expect(script, contains('<triggers>'));
      expect(script, contains('com.cloudbees.jenkins.GitHubPushTrigger'));
      expect(script, isNot(contains('hudson.triggers.SCMTrigger')));
      expect(script, contains('./scripts/build-certified-macos.sh'));
      expect(
        script,
        contains('build/macos/Build/Products/Release/AppSok-certified.zip'),
      );
      expect(
        script,
        contains(
          'build/macos/Build/Products/Release/AppSok-certified.zip.sha256',
        ),
      );
      expect(script, contains('<numToKeep>10</numToKeep>'));
      expect(script, contains('<artifactNumToKeep>10</artifactNumToKeep>'));
    });

    test('jenkins job upsert keeps credentials out of generated config', () {
      final script = _readScript('scripts/upsert-jenkins-certified-job.sh');
      final setupScript = _readScript('scripts/setup-appsok-ci-secrets.sh');

      expect(script, contains('sops -d'));
      expect(script, isNot(contains('jenkins_api_token=')));
      expect(script, isNot(contains(r'-u "$JENKINS_USER:$JENKINS_TOKEN"')));
      expect(
        script,
        isNot(contains(r'python3 - "$JENKINS_USER" "$JENKINS_TOKEN"')),
      );
      expect(script, contains(r'CURL_CONFIG="$(mktemp)"'));
      expect(script, contains(r'--config "$CURL_CONFIG"'));
      expect(script, contains("trap 'rm -f \"\$CURL_CONFIG\"' EXIT"));
      expect(script, contains('APPLY=false'));
      expect(script, contains('DRY_RUN=true'));
      expect(script, contains('--apply'));
      expect(script, contains('--dry-run'));

      for (final key in [
        'jenkins_url',
        'jenkins_username',
        'jenkins_api_token',
      ]) {
        expect(script, contains('read_secret'));
        expect(script, contains(key));
        expect(setupScript, contains('"$key": os.environ'));
      }
    });

    test(
        'jenkins job upsert escapes SCM URL in dry-run XML',
        () {
          final result = Process.runSync('bash', [
            '-c',
            'APPSOK_SCM_URL="https://example.com/toki/appsok.git?foo=1\u0026bar=\u003ctag\u003e" bash scripts/upsert-jenkins-certified-job.sh --dry-run',
          ]);

          expect(result.exitCode, 0, reason: 'dry-run should exit 0');
          final output = result.stdout as String;

          // Check that XML special characters are escaped.
          // Python html.escape: & -> &amp;, < -> &lt;, > -> &gt;.
          expect(output, contains('\u0026amp;'));
          expect(output, contains('\u0026lt;'));
          expect(output, contains('\u0026gt;'));

          // Check that the raw unescaped values do NOT appear in <url> content
          final urlMatch = RegExp(r'<url>\s*(.*?)\s*</url>')
              .firstMatch(output.replaceAll('\r\n', '\n'));
          expect(urlMatch, isNotNull, reason: '<url> tag should exist in XML');
          final urlContent = urlMatch?.group(1) ?? '';
          expect(urlContent, isNot(contains('&bar=<tag>')));
          expect(urlContent, isNot(contains('&foo=1&')));
        },
        timeout: const Timeout(Duration(seconds: 30)));

    test(
        'jenkins job upsert rejects HTTP SCM URLs with userinfo',
        () {
          final result = Process.runSync('bash', [
            '-c',
            'APPSOK_SCM_URL="https://user:token@example.com/org/repo.git" bash scripts/upsert-jenkins-certified-job.sh --dry-run',
          ]);

          expect(result.exitCode, isNot(0),
              reason: 'should reject URLs with userinfo');
          final stderr = (result.stderr as String?) ?? '';
          final stdout = (result.stdout as String?) ?? '';

          // Credential-free error message
          expect(
            stderr + stdout,
            contains('credential-free'),
            reason: 'should contain credential-free error message',
          );

          // Token must not appear in stdout or stderr
          expect(stdout, isNot(contains('token')));
          expect(stderr, isNot(contains('token')));
        },
        timeout: const Timeout(Duration(seconds: 30)));
  });
}

String _readScript(String path) => File(path).readAsStringSync();

void _expectOrder(String source, String before, String after) {
  final beforeIndex = _requiredIndex(source, before);
  final afterIndex = _requiredIndex(source, after);
  expect(
    beforeIndex,
    lessThan(afterIndex),
    reason: '"$before" must appear before "$after"',
  );
}

int _requiredIndex(String source, String needle) {
  final index = source.indexOf(needle);
  expect(index, greaterThanOrEqualTo(0), reason: 'missing "$needle"');
  return index;
}
