import 'dart:convert';

import '../command_runner.dart';
import '../models/sessions_snapshot.dart';

/// Thrown by [SessionService.fetchSnapshot] on any non-zero exit, timeout,
/// or malformed-JSON condition — callers (namely [StatusRepository]) use
/// this to distinguish "no fresh data this poll" from a genuine bug.
class StatusFetchException implements Exception {
  final String message;
  StatusFetchException(this.message);

  @override
  String toString() => 'StatusFetchException: $message';
}

/// Wraps `status.sh --json` as safely-parsed [SessionsSnapshot] models.
/// This is the single place that knows the on-disk path to `status.sh` and
/// the shape of its JSON output — nothing else in `mam_core` reads YAML,
/// SQLite, or jsonl directly (Rev.1 §1 principle).
class SessionService {
  final String statusScriptPath;
  final Duration timeout;

  SessionService({
    required this.statusScriptPath,
    this.timeout = const Duration(seconds: 5),
  });

  bool _preflightChecked = false;

  Future<void> _checkPreflight() async {
    if (_preflightChecked) return;

    final bashResult = await runCommand(['bash', '--version'], timeout: timeout);
    if (bashResult.rc != 0) {
      throw StatusFetchException('preflight failed: bash is missing or not executable');
    }

    final tmuxResult = await runCommand(['tmux', '-V'], timeout: timeout);
    if (tmuxResult.rc != 0) {
      throw StatusFetchException('preflight failed: tmux is missing or not executable');
    }

    _preflightChecked = true;
  }

  Future<SessionsSnapshot> fetchSnapshot() async {
    await _checkPreflight();

    final result = await runCommand(
      ['bash', statusScriptPath, '--json'],
      timeout: timeout,
    );

    if (result.timedOut) {
      throw StatusFetchException('status.sh --json timed out after $timeout');
    }
    if (result.rc != 0) {
      throw StatusFetchException(
        'status.sh --json exited ${result.rc}: ${result.stderr.trim()}',
      );
    }

    final Object? decoded;
    try {
      decoded = jsonDecode(result.stdout);
    } on FormatException catch (e) {
      throw StatusFetchException('malformed JSON from status.sh --json: $e');
    }

    if (decoded is! Map<String, dynamic>) {
      throw StatusFetchException('status.sh --json returned non-object JSON');
    }

    return SessionsSnapshot.fromJson(decoded);
  }
}
