// Build a zip diagnostic pack the user can hand back for remote debugging.

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

import 'package:archive/archive.dart';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';

import '../editor/canvas/input_diagnostics.dart';
import '../editor/input/pen_input_service.dart';
import 'badnote_log.dart';
import 'frame_sampler.dart';
import 'pen_event_ring.dart';

class DiagnosticExportResult {
  DiagnosticExportResult({required this.zipPath, required this.bytes});

  final String zipPath;
  final int bytes;
}

class DiagnosticExport {
  DiagnosticExport._();
  static final DiagnosticExport instance = DiagnosticExport._();

  /// Flush logs and write a zip under Documents/badnote_diagnostics/.
  Future<DiagnosticExportResult> exportPack({
    Duration penWindow = const Duration(minutes: 5),
  }) async {
    final log = BadNoteLog.instance;
    await log.flush();

    final meta = <String, Object?>{
      'exportedAt': DateTime.now().toIso8601String(),
      'sessionId': log.sessionId,
      'platform': Platform.operatingSystem,
      'osVersion': Platform.operatingSystemVersion,
      'localHostname': Platform.localHostname,
      'numberOfProcessors': Platform.numberOfProcessors,
      'flutter': {
        'foundationDebug': kDebugMode,
        'foundationProfile': kProfileMode,
        'foundationRelease': kReleaseMode,
      },
      'penNative': PenInputService.instance.debugSummary,
      'penActive': PenInputService.instance.isActive,
      'zoom': InputDiagnostics.instance.summary(),
      'frames': FrameSampler.instance.summary(),
      'instructions':
          'Reproduce the issue for ~3 minutes with diagnostics on, then share this zip.',
    };

    final archive = Archive();
    void addText(String name, String body) {
      final bytes = utf8.encode(body);
      archive.addFile(ArchiveFile(name, bytes.length, bytes));
    }

    addText('meta.json', const JsonEncoder.withIndent('  ').convert(meta));
    addText(
      'pen_events.json',
      const JsonEncoder.withIndent('  ').convert(
        PenEventRing.instance.toJsonList(window: penWindow),
      ),
    );
    addText(
      'frame_samples.json',
      const JsonEncoder.withIndent('  ').convert(FrameSampler.instance.toJsonList()),
    );
    addText(
      'log_ring.json',
      const JsonEncoder.withIndent('  ').convert(log.snapshotRing()),
    );

    // Include on-disk session NDJSON if present.
    final sessionPath = log.path;
    if (sessionPath != null) {
      try {
        final f = File(sessionPath);
        if (await f.exists()) {
          final bytes = await f.readAsBytes();
          archive.addFile(
            ArchiveFile('session.ndjson', bytes.length, bytes),
          );
        }
      } catch (_) {}
    }

    // Legacy input log if it exists alongside.
    try {
      Directory dir;
      try {
        dir = await getApplicationDocumentsDirectory();
      } catch (_) {
        dir = await getTemporaryDirectory();
      }
      final legacy = File(
        '${dir.path}${Platform.pathSeparator}badnote_input_log.txt',
      );
      if (await legacy.exists()) {
        final bytes = await legacy.readAsBytes();
        archive.addFile(
          ArchiveFile('legacy_input_log.txt', bytes.length, bytes),
        );
      }
    } catch (_) {}

    final encoded = ZipEncoder().encode(archive);
    if (encoded.isEmpty) {
      throw StateError('Failed to encode diagnostic zip');
    }

    Directory outDir = log.directory ??
        Directory(
          '${(await getApplicationDocumentsDirectory()).path}'
          '${Platform.pathSeparator}badnote_diagnostics',
        );
    if (!await outDir.exists()) {
      await outDir.create(recursive: true);
    }
    final stamp = DateTime.now()
        .toIso8601String()
        .replaceAll(':', '-')
        .replaceAll('.', '-');
    final zipPath =
        '${outDir.path}${Platform.pathSeparator}badnote_diag_$stamp.zip';
    await File(zipPath).writeAsBytes(encoded, flush: true);

    BadNoteLog.instance.info(
      LogSubsystem.diag,
      'export_pack',
      fields: {'path': zipPath, 'bytes': encoded.length},
    );

    return DiagnosticExportResult(zipPath: zipPath, bytes: encoded.length);
  }
}
