import 'dart:async';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:recording_tool/features/recording/platform/recording_channel_names.dart';

enum RecordingState {
  idle,
  previewing,
  recording,
  stopping,
  error;

  static RecordingState fromRaw(String? raw) {
    return RecordingState.values.firstWhere(
      (value) => value.name == raw,
      orElse: () => RecordingState.idle,
    );
  }
}

class RecordingStatus {
  const RecordingStatus({
    required this.state,
    this.outputPath,
    this.elapsedMillis = 0,
    this.message,
  });

  final RecordingState state;
  final String? outputPath;
  final int elapsedMillis;
  final String? message;

  factory RecordingStatus.fromMap(Map<dynamic, dynamic> map) {
    return RecordingStatus(
      state: RecordingState.fromRaw(map['state'] as String?),
      outputPath: map['outputPath'] as String?,
      elapsedMillis: (map['elapsedMillis'] as num?)?.toInt() ?? 0,
      message: map['message'] as String?,
    );
  }

  bool get isRecording => state == RecordingState.recording;
}

class RecordingPlatform {
  RecordingPlatform._();

  static const MethodChannel _channel = MethodChannel(
    RecordingChannelNames.method,
  );
  static const EventChannel _events = EventChannel(
    RecordingChannelNames.events,
  );

  static bool get isSupported =>
      supportsHost(isAndroid: Platform.isAndroid, isIOS: Platform.isIOS);

  static bool supportsHost({required bool isAndroid, required bool isIOS}) {
    return isAndroid || isIOS;
  }

  static Stream<RecordingStatus>? _statusStream;

  static Stream<RecordingStatus> statusStream() {
    if (!isSupported) {
      return const Stream.empty();
    }
    _statusStream ??= _events.receiveBroadcastStream().map(
      (event) =>
          RecordingStatus.fromMap(Map<dynamic, dynamic>.from(event as Map)),
    );
    return _statusStream!;
  }

  static Future<RecordingStatus> initializePreview() async {
    final result = await _channel.invokeMapMethod<String, dynamic>(
      'initializePreview',
    );
    return RecordingStatus.fromMap(result ?? const {});
  }

  static Future<RecordingZoomCapabilities> getZoomCapabilities() async {
    final result = await _channel.invokeMapMethod<String, dynamic>(
      'getZoomCapabilities',
    );
    return RecordingZoomCapabilities.fromMap(result);
  }

  static Future<RecordingZoomCapabilities> setZoomRatio(double ratio) async {
    final result = await _channel.invokeMapMethod<String, dynamic>(
      'setZoomRatio',
      <String, dynamic>{'zoomRatio': ratio},
    );
    return RecordingZoomCapabilities.fromMap(result);
  }

  static Future<RecordingStartResult> startRecording({
    bool withAudio = true,
    bool enableDoNotDisturb = true,
    String? displayName,
  }) async {
    final args = <String, dynamic>{
      'withAudio': withAudio,
      'enableDoNotDisturb': enableDoNotDisturb,
    };
    if (displayName != null) {
      args['displayName'] = displayName;
    }

    final result = await _channel.invokeMapMethod<String, dynamic>(
      'startRecording',
      args,
    );
    return RecordingStartResult(
      outputPath: result?['outputPath'] as String?,
      status: RecordingStatus.fromMap(
        Map<dynamic, dynamic>.from(result?['status'] as Map? ?? const {}),
      ),
    );
  }

  static Future<RecordingStopResult> stopRecording() async {
    final result = await _channel.invokeMapMethod<String, dynamic>(
      'stopRecording',
    );
    return RecordingStopResult.fromMap(result);
  }

  static Future<void> disposePreview() =>
      _channel.invokeMethod('disposePreview');

  static Future<bool> hasNotificationPolicyAccess() async {
    return await _channel.invokeMethod<bool>('hasNotificationPolicyAccess') ??
        false;
  }

  static Future<void> openNotificationPolicySettings() {
    return _channel.invokeMethod('openNotificationPolicySettings');
  }

  static Future<bool> enableDoNotDisturb() async {
    return await _channel.invokeMethod<bool>('enableDoNotDisturb') ?? false;
  }

  static Future<void> disableDoNotDisturb() {
    return _channel.invokeMethod('disableDoNotDisturb');
  }

  static Future<bool> isIgnoringBatteryOptimizations() async {
    return await _channel.invokeMethod<bool>(
          'isIgnoringBatteryOptimizations',
        ) ??
        true;
  }

  static Future<void> openBatteryOptimizationSettings() {
    return _channel.invokeMethod('openBatteryOptimizationSettings');
  }

  static Future<void> setImmersiveMode({required bool enabled}) {
    return _channel.invokeMethod('setImmersiveMode', <String, dynamic>{
      'enabled': enabled,
    });
  }

  static Future<RecordingStatus> getStatus() async {
    final result = await _channel.invokeMapMethod<String, dynamic>('getStatus');
    return RecordingStatus.fromMap(result ?? const {});
  }
}

class RecordingZoomCapabilities {
  const RecordingZoomCapabilities({
    required this.zoomRatio,
    required this.minZoomRatio,
    required this.maxZoomRatio,
  });

  final double zoomRatio;
  final double minZoomRatio;
  final double maxZoomRatio;

  factory RecordingZoomCapabilities.fromMap(Map<String, dynamic>? map) {
    final minZoomRatio = (map?['minZoomRatio'] as num?)?.toDouble() ?? 1.0;
    final maxZoomRatio = (map?['maxZoomRatio'] as num?)?.toDouble() ?? 3.0;
    final zoomRatio = (map?['zoomRatio'] as num?)?.toDouble() ?? minZoomRatio;
    return RecordingZoomCapabilities(
      zoomRatio: zoomRatio.clamp(minZoomRatio, maxZoomRatio).toDouble(),
      minZoomRatio: minZoomRatio,
      maxZoomRatio: maxZoomRatio,
    );
  }
}

class RecordingStartResult {
  const RecordingStartResult({this.outputPath, required this.status});

  final String? outputPath;
  final RecordingStatus status;
}

class RecordingStopResult {
  const RecordingStopResult({
    this.outputPath,
    required this.status,
    this.gallerySaved = true,
    this.galleryErrorMessage,
  });

  final String? outputPath;
  final RecordingStatus status;
  final bool gallerySaved;
  final String? galleryErrorMessage;

  factory RecordingStopResult.fromMap(Map<String, dynamic>? result) {
    return RecordingStopResult(
      outputPath: result?['outputPath'] as String?,
      status: RecordingStatus.fromMap(
        Map<dynamic, dynamic>.from(result?['status'] as Map? ?? const {}),
      ),
      gallerySaved: result?['gallerySaved'] as bool? ?? true,
      galleryErrorMessage: result?['galleryErrorMessage'] as String?,
    );
  }
}
