import 'dart:async';

import '../../programs/models/program.dart';
import '../../programs/models/step.dart';
import 'device_log.dart';
import 'device_message.dart';
import 'device_message_service.dart';
import 'runner_interface.dart';
import 'task_payload.dart';

/// JSON 协议运行器
///
/// 与下位机的程序运行相关通信（create_task / control）通过
/// [DeviceMessageService] 完成；运行过程中下位机可通过 ack 消息确认动作，
/// 步骤进度仍由下位机主动上报（具体协议待硬件侧确认）。
///
/// 当前实现：
/// 1. start  → 发送 `create_task` 消息（need_ack=true），收到 ack 后切到 running；
/// 2. pause  → 发送 `control{status:pause}`，切到 paused；
/// 3. resume → 发送 `control{status:continue}`，切到 running；
/// 4. stop   → 发送 `control{status:stop}`，切到 idle。
///
/// 为兜底下位机不主动上报完成的情况，保留本地倒计时 + 步骤推进。
class JsonSerialRunner implements Runner {
  @override
  RunnerStatus status = RunnerStatus.idle;

  final DeviceMessageService _msg;
  // ignore: unused_field 持有当前运行的程序引用，便于调试与未来扩展
  Program? _program;
  List<Step> _steps = const [];
  RunnerCallbacks? _callbacks;

  void Function()? _cancelAckSub;
  Timer? _ticker;
  int _currentStepIndex = 0;
  int _remainingSeconds = 0;
  String? _pendingCreateTaskId;
  String? _pendingControlId;

  JsonSerialRunner({required DeviceMessageService messageService})
      : _msg = messageService {
    _cancelAckSub = _msg.subscribe(DeviceMessageType.createTask, _onCreateTaskAck);
    _msg.subscribe(DeviceMessageType.control, _onControlAck);
  }

  @override
  void start(Program program, List<Step> steps, RunnerCallbacks callbacks) {
    if (status == RunnerStatus.running) {
      callbacks.onError?.call('已有程序在运行中');
      return;
    }
    if (steps.isEmpty) {
      callbacks.onError?.call('程序步骤为空');
      status = RunnerStatus.error;
      return;
    }

    _program = program;
    _steps = List.unmodifiable(steps);
    _callbacks = callbacks;
    _currentStepIndex = 0;
    _remainingSeconds = _stepTotalSeconds(steps[0]);

    final payload = TaskPayload.fromProgram(program, steps);
    final messageId = _msg.nextId();
    _pendingCreateTaskId = messageId;
    final msg = payload.toMessage(messageId, needAck: true);
    // 乐观更新：与 RunStateNotifier 的运行态保持一致，
    // 避免在 create_task 应答到达前的窗口里，pause/stop 被状态守卫静默丢弃。
    status = RunnerStatus.running;
    DeviceLog.info('Runner.start: program="${program.name}" steps=${steps.length} '
        'temperature=${program.temperature} airflow=${program.airflowTime}');
    _msg.send(msg).then((ok) {
      if (!ok) {
        _pendingCreateTaskId = null;
        status = RunnerStatus.error;
        _callbacks?.onError?.call('下发任务失败：串口写入错误');
      }
    });
  }

  @override
  void pause() {
    if (status != RunnerStatus.running) return;
    DeviceLog.info('Runner.pause');
    _sendControl('pause');
    _stopLocalTicker();
    status = RunnerStatus.paused;
  }

  @override
  void resume() {
    if (status != RunnerStatus.paused) return;
    DeviceLog.info('Runner.resume');
    _sendControl('continue');
    status = RunnerStatus.running;
    _startLocalTicker();
  }

  @override
  void stop() {
    if (status == RunnerStatus.idle) return;
    DeviceLog.info('Runner.stop');
    _sendControl('stop');
    _teardown();
    status = RunnerStatus.idle;
  }

  @override
  RunnerStatus getStatus() => status;

  @override
  void dispose() {
    _teardown();
    _cancelAckSub?.call();
    _cancelAckSub = null;
  }

  // -- 私有方法 ---------------------------------------------------------

  void _sendControl(String statusValue) {
    final messageId = _msg.nextId();
    _pendingControlId = messageId;
    final msg = DeviceMessage.request(
      messageId: messageId,
      type: DeviceMessageType.control,
      data: {'status': statusValue},
      needAck: true,
    );
    _msg.send(msg);
  }

  void _onCreateTaskAck(DeviceMessage ack) {
    if (ack.ack != _pendingCreateTaskId) return;
    _pendingCreateTaskId = null;
    DeviceLog.info('Runner received create_task ack: id=${ack.messageId}');
    // 状态已由 start() 乐观置为 running，此处仅启动本地兜底倒计时。
    // 若发送失败，.then() 已将 status 置为 error，不应再启动倒计时。
    if (status == RunnerStatus.running) {
      _startLocalTicker();
    }
  }

  void _onControlAck(DeviceMessage ack) {
    if (ack.ack != _pendingControlId) return;
    _pendingControlId = null;
    DeviceLog.info('Runner received control ack: id=${ack.messageId}');
    // control ack 不修改状态，状态机在调用 pause/resume/stop 时已切过
  }

  // -- 本地兜底倒计时 ---------------------------------------------------

  void _startLocalTicker() {
    _ticker?.cancel();
    _ticker = Timer.periodic(const Duration(seconds: 1), (_) {
      if (status != RunnerStatus.running) return;
      if (_steps.isEmpty) return;
      _remainingSeconds--;
      if (_remainingSeconds <= 0) {
        _currentStepIndex++;
        if (_currentStepIndex >= _steps.length) {
          DeviceLog.info('Runner 本地倒计时完成 (timeout fallback)');
          _stopLocalTicker();
          status = RunnerStatus.completed;
          _callbacks?.onComplete?.call();
          return;
        }
        _remainingSeconds = _stepTotalSeconds(_steps[_currentStepIndex]);
      }
      final well = _steps[_currentStepIndex.clamp(0, _steps.length - 1)]
          .position;
      _callbacks?.onProgress?.call(
        _currentStepIndex,
        _remainingSeconds,
        _calculateProgress(_currentStepIndex, _remainingSeconds),
        well,
      );
    });
  }

  void _stopLocalTicker() {
    _ticker?.cancel();
    _ticker = null;
  }

  void _teardown() {
    _stopLocalTicker();
    _program = null;
    _steps = const [];
    _callbacks = null;
    _pendingCreateTaskId = null;
    _pendingControlId = null;
  }

  int _stepTotalSeconds(Step s) {
    final t = s.mixTime + s.magnetTime + s.blowTime;
    return t == 0 ? 5 : t;
  }

  double _calculateProgress(int stepIndex, int remaining) {
    if (_steps.isEmpty) return 0;
    var total = 0;
    for (final s in _steps) {
      total += _stepTotalSeconds(s);
    }
    if (total <= 0) return 0;
    var elapsed = 0;
    for (var i = 0; i < stepIndex && i < _steps.length; i++) {
      elapsed += _stepTotalSeconds(_steps[i]);
    }
    final cur = _stepTotalSeconds(_steps[stepIndex.clamp(0, _steps.length - 1)]);
    elapsed += (cur - remaining).clamp(0, cur);
    return (elapsed / total).clamp(0.0, 1.0);
  }
}
