import 'dart:async';

import 'package:dio/dio.dart';
import 'package:logger/logger.dart';
import 'package:rxdart/rxdart.dart';

import '../../domain/entities/alarm_state.dart';
import '../../domain/entities/server_config.dart';
import '../models/bridge_event.dart';

/// Service for querying and tracking alarm state from GeViServer
class AlarmService {
  final Logger _logger = Logger();
  final Map<String, Dio> _httpClients = {};
  final Map<String, ServerConfig> _servers = {};

  // Alarm state
  final _alarmsController = BehaviorSubject<List<AlarmState>>.seeded([]);
  Timer? _syncTimer;

  /// Stream of active alarms
  Stream<List<AlarmState>> get alarms => _alarmsController.stream;

  /// Current active alarms
  List<AlarmState> get currentAlarms => _alarmsController.value;

  /// Initialize with GeViServer configurations
  Future<void> initialize(List<ServerConfig> servers) async {
    // Only use GeViServer type servers for alarm queries
    final geviServers =
        servers.where((s) => s.type == ServerType.geviserver && s.enabled);

    for (final server in geviServers) {
      _servers[server.id] = server;
      _httpClients[server.id] = Dio(BaseOptions(
        baseUrl: server.bridgeUrl,
        connectTimeout: const Duration(seconds: 5),
        receiveTimeout: const Duration(seconds: 10),
      ));
    }

    _logger.i('AlarmService initialized with ${_servers.length} GeViServers');
  }

  /// Query all active alarms from all GeViServers
  Future<List<AlarmState>> queryAllAlarms() async {
    final allAlarms = <AlarmState>[];

    for (final entry in _servers.entries) {
      final serverId = entry.key;
      try {
        final alarms = await _queryAlarmsFromServer(serverId);
        allAlarms.addAll(alarms);
        _logger.i('Queried ${alarms.length} alarms from $serverId');
      } catch (e) {
        _logger.e('Failed to query alarms from $serverId: $e');
      }
    }

    _alarmsController.add(allAlarms);
    return allAlarms;
  }

  /// Query alarms from a specific server using GetFirstAlarm/GetNextAlarm pattern
  Future<List<AlarmState>> _queryAlarmsFromServer(String serverId) async {
    final client = _httpClients[serverId];
    if (client == null) return [];

    final alarms = <AlarmState>[];

    try {
      // Try the bulk endpoint first (preferred)
      final response = await client.get('/alarms/active');
      if (response.statusCode == 200) {
        final data = response.data as Map<String, dynamic>;
        final alarmList = data['alarms'] as List<dynamic>? ?? [];

        for (final alarmJson in alarmList) {
          final alarm = _parseAlarmFromJson(
            alarmJson as Map<String, dynamic>,
            serverId,
          );
          if (alarm != null) {
            alarms.add(alarm);
          }
        }
        return alarms;
      }
    } catch (e) {
      _logger.d('Bulk alarm query failed, trying iteration: $e');
    }

    // Fallback to GetFirstAlarm/GetNextAlarm iteration
    try {
      // Get first alarm
      var response = await client.get('/alarms/first');
      if (response.statusCode != 200) return alarms;

      var data = response.data as Map<String, dynamic>;
      if (data['alarm'] == null) return alarms; // No alarms

      var alarm = _parseAlarmFromJson(
        data['alarm'] as Map<String, dynamic>,
        serverId,
      );
      if (alarm != null) alarms.add(alarm);

      // Iterate through remaining alarms
      while (true) {
        response = await client.get('/alarms/next');
        if (response.statusCode != 200) break;

        data = response.data as Map<String, dynamic>;
        if (data['alarm'] == null) break; // No more alarms

        alarm = _parseAlarmFromJson(
          data['alarm'] as Map<String, dynamic>,
          serverId,
        );
        if (alarm != null) alarms.add(alarm);

        // Safety limit
        if (alarms.length > 1000) {
          _logger.w('Alarm query hit safety limit of 1000');
          break;
        }
      }
    } catch (e) {
      _logger.e('Alarm iteration failed: $e');
    }

    return alarms;
  }

  /// Parse alarm from JSON response
  AlarmState? _parseAlarmFromJson(Map<String, dynamic> json, String serverId) {
    try {
      return AlarmState(
        eventId: json['EventID'] as int? ?? json['event_id'] as int? ?? 0,
        eventName:
            json['EventName'] as String? ?? json['event_name'] as String? ?? '',
        typeId: json['TypeID'] as int? ?? json['type_id'] as int? ?? 0,
        foreignKey: _parseForeignKey(json['ForeignKey'] ?? json['foreign_key']),
        serverId: serverId,
        startedAt: _parseDateTime(json['StartedAt'] ?? json['started_at']),
        stoppedAt: _parseNullableDateTime(json['StoppedAt'] ?? json['stopped_at']),
        isActive: json['IsActive'] as bool? ?? json['is_active'] as bool? ?? true,
        status: AlarmStatus.fromValue(
          json['Status'] as int? ?? json['status'] as int? ?? 0,
        ),
        associatedMonitor: json['AssociatedMonitor'] as int? ??
            json['associated_monitor'] as int?,
      );
    } catch (e) {
      _logger.e('Failed to parse alarm: $e');
      return null;
    }
  }

  int _parseForeignKey(dynamic value) {
    if (value is int) return value;
    if (value is String) return int.tryParse(value) ?? 0;
    return 0;
  }

  DateTime _parseDateTime(dynamic value) {
    if (value is DateTime) return value;
    if (value is String) return DateTime.tryParse(value) ?? DateTime.now();
    return DateTime.now();
  }

  DateTime? _parseNullableDateTime(dynamic value) {
    if (value == null) return null;
    if (value is DateTime) return value;
    if (value is String) return DateTime.tryParse(value);
    return null;
  }

  /// Start periodic alarm sync
  void startPeriodicSync(Duration interval) {
    stopPeriodicSync();
    _syncTimer = Timer.periodic(interval, (_) => queryAllAlarms());
    _logger.i('Started periodic alarm sync every ${interval.inSeconds}s');
  }

  /// Stop periodic alarm sync
  void stopPeriodicSync() {
    _syncTimer?.cancel();
    _syncTimer = null;
  }

  /// Handle alarm event from WebSocket
  void handleAlarmEvent(BridgeEvent event) {
    final currentAlarms = List<AlarmState>.from(_alarmsController.value);

    if (event.isEventStarted) {
      // New alarm started
      final newAlarm = AlarmState(
        eventId: event.eventId ?? 0,
        eventName: event.eventName ?? '',
        typeId: event.typeId ?? 0,
        foreignKey: event.foreignKey ?? 0,
        serverId: event.serverId,
        startedAt: event.timestamp,
        isActive: true,
        status: AlarmStatus.newAlarm,
      );

      // Remove existing alarm with same ID if any
      currentAlarms.removeWhere((a) =>
          a.eventId == newAlarm.eventId && a.serverId == newAlarm.serverId);
      currentAlarms.add(newAlarm);

      _logger.d('Alarm started: ${newAlarm.eventName} (ID: ${newAlarm.eventId})');
    } else if (event.isEventStopped) {
      // Alarm stopped
      final index = currentAlarms.indexWhere((a) =>
          a.eventId == event.eventId && a.serverId == event.serverId);

      if (index >= 0) {
        currentAlarms[index] = currentAlarms[index].stopped();
        _logger.d('Alarm stopped: ${currentAlarms[index].eventName}');
      }
    } else if (event.isAlarmQueueNotification) {
      // Alarm status changed (presented, confirmed, etc.)
      final alarmId = event.alarmId;
      final notification = event.notification;

      if (alarmId != null && notification != null) {
        final index = currentAlarms.indexWhere((a) =>
            a.eventId == alarmId && a.serverId == event.serverId);

        if (index >= 0) {
          final newStatus = AlarmStatus.fromValue(notification);
          currentAlarms[index] = currentAlarms[index].withStatus(newStatus);
          _logger.d(
              'Alarm status changed: ${currentAlarms[index].eventName} -> ${newStatus.name}');
        }
      }
    }

    _alarmsController.add(currentAlarms);
  }

  /// Get alarms blocking a specific monitor
  List<AlarmState> getAlarmsForMonitor(int monitorId) {
    return _alarmsController.value
        .where((a) => a.associatedMonitor == monitorId && a.blocksMonitor)
        .toList();
  }

  /// Check if any alarm blocks a monitor
  bool isMonitorBlocked(int monitorId) {
    return _alarmsController.value
        .any((a) => a.associatedMonitor == monitorId && a.blocksMonitor);
  }

  /// Dispose resources
  void dispose() {
    stopPeriodicSync();
    _alarmsController.close();
    _httpClients.clear();
    _servers.clear();
  }
}
