import 'dart:collection';

import 'package:flutter/material.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/src/extension_navigation.dart';
import 'package:get/get_navigation/src/routes/observers/route_observer.dart';
import 'package:guru_utils/controller/aware/keep_screen_on_aware.dart';
import 'package:guru_utils/lifecycle/lifecycle_manager.dart';
import 'package:guru_utils/router/router.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/tuple/tuple.dart';
import 'base_controller.dart';
import 'package:guru_utils/aigc/bi/ai_bi.dart';

/// Created by Haoyi on 2022/1/30
///

enum LifecycleState { create, paused, resumed }

class LifecycleEvent {
  const LifecycleEvent();
}

enum LifecycleActionResult { consumed, suspend, failed }

typedef LifecycleInvoker = Future<LifecycleActionResult> Function();

class LifecycleAction {
  final LifecycleInvoker invoker;

  LifecycleAction({required this.invoker});

  Future<LifecycleActionResult> invoke() async {
    try {
      return await invoker();
    } catch (error, stacktrace) {
      Log.w("invoke error! $error");
    }
    return LifecycleActionResult.failed;
  }
}

class RouteInfo {
  final bool topRoute;
  final Routing? routing;

  const RouteInfo(this.topRoute, {this.routing});

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is RouteInfo &&
          runtimeType == other.runtimeType &&
          topRoute == other.topRoute &&
          routing == other.routing;

  @override
  int get hashCode => topRoute.hashCode ^ routing.hashCode;
}

class LifecycleInfo {
  final LifecycleSnapshot snapshot;
  final bool topRoute;

  LifecycleState get state => snapshot.state;

  bool get foreground => snapshot.foreground;

  Routing get routing => snapshot.routing;

  const LifecycleInfo(this.snapshot, this.topRoute);

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is LifecycleInfo &&
          runtimeType == other.runtimeType &&
          snapshot == other.snapshot &&
          topRoute == other.topRoute;

  @override
  int get hashCode => snapshot.hashCode ^ topRoute.hashCode;

  bool isResumed() {
    return snapshot.state == LifecycleState.resumed && snapshot.foreground && topRoute;
  }

  @override
  String toString() {
    return 'LifecycleInfo{snapshot: $snapshot, topRoute: $topRoute}';
  }
}

abstract class LifecycleController extends BaseController with LifecycleObserver {
  bool _dispatching = false;
  bool _paused = true;
  final DoubleLinkedQueue<LifecycleAction> actions = DoubleLinkedQueue();

  late Uri routeUri;
  final BehaviorSubject<LifecycleState> lifecycleState =
      BehaviorSubject.seeded(LifecycleState.create);

  final BehaviorSubject<LifecycleInfo> lifecycleInfo =
      BehaviorSubject.seeded(LifecycleInfo(LifecycleSnapshot.invalid, false));

  bool get isResumed => currentLifecycleInfo.isResumed();

  LifecycleInfo get currentLifecycleInfo => lifecycleInfo.value;

  bool get isTopRoute => _checkIsTopRoute();

  // Stream<LifecycleState> get observableLifecycleState => lifecycleState.stream;

  Stream<LifecycleInfo> get observableLifecycleInfo => lifecycleInfo.stream;

  bool _checkIsTopRoute() {
    try {
      final top = buildCurrentRouteUri();
      return top.path == routeUri.path;
    } catch (error) {
      return false;
    }
  }

  Uri buildCurrentRouteUri() {
    final route = Get.rawRoute;
    if (route != null) {
      return Uri.parse("${route.settings.name}");
    }

    return Uri.parse(Get.currentRoute);
  }

  Route<dynamic>? get rawRoute => Get.rawRoute;

  Future<bool> waitingResume() {
    if (isResumed) {
      return Future.value(true);
    }
    Log.w("waiting resume...$currentLifecycleInfo", tag: "Game");
    return observableLifecycleInfo.firstWhere((info) {
      Log.d("waiting resume check:$info $isResumed");
      return isResumed;
    }).then((value) => true);
  }

  void _refreshLifecycle(LifecycleInfo info) {
    final latestInfo = currentLifecycleInfo;

    Log.i(
        "[$runtimeType] refresh lifecycle! $isTopRoute $isResumed ${info.routing.current} $latestInfo => $info");

    lifecycleInfo.addIfChanged(info);
    if (!latestInfo.foreground && info.foreground) {
      onAppForeground();
      AiBi.instance.applife(true);
    }
    if (!latestInfo.isResumed() && info.isResumed()) {
      _dispatchResumed();
      AiBi.instance.lifecycle(runtimeType.toString().replaceAll("Controller", "Page"), true);
    } else if (latestInfo.isResumed() && !info.isResumed()) {
      _dispatchPaused();
      AiBi.instance.lifecycle(runtimeType.toString().replaceAll("Controller", "Page"), false);
    }
    if (latestInfo.foreground && !info.foreground) {
      onAppBackground();
      AiBi.instance.applife(false);
    }
  }

  @override
  @mustCallSuper
  void onInit() {
    super.onInit();
    addSubscription(LifecycleManager.instance.observableLifecycleEvent.listen((event) {
      onLifecycleEvent(event);
    }));
    if (this is KeepScreenOnAware) {
      (this as KeepScreenOnAware).initKeepScreenOn();
    }
  }

  @override
  void onReady() {
    super.onReady();
    LifecycleManager.instance.addLifecycleObserver(this);
    routeUri = buildCurrentRouteUri();
    Log.d("[$runtimeType] onReady:$_paused $routeUri");
    delayed(LifecycleManager.verifyLifecycleDuration, () {
      final snapshot = LifecycleManager.instance.currentLifecycleSnapshot;
      final info = LifecycleInfo(snapshot, _checkIsTopRoute());
      final changed = info != currentLifecycleInfo;
      Log.d("[$runtimeType] onReady delayed: $_paused $snapshot $routeUri $changed");
      if (_paused || changed) {
        _refreshLifecycle(info);
      }
    });
  }

  void postAction(LifecycleAction action) {
    actions.add(action);
    if (isResumed) {
      _dispatchLifecycleAction();
    }
  }

  void postActionDelayed(Duration duration, LifecycleAction action) {
    delayed(duration, () {
      postAction(action);
    });
  }

  void onLifecycleEvent(LifecycleEvent event) {}

  @override
  void onLifecycleChanged(LifecycleSnapshot snapshot) {
    Log.d("[$runtimeType] onLifecycleChanged:$snapshot");
    _refreshLifecycle(LifecycleInfo(snapshot, _checkIsTopRoute()));
  }

  @mustCallSuper
  @override
  void onClose() {
    Log.d("[$runtimeType] onClose:$_paused");
    LifecycleManager.instance.removeLifecycleObserver(this);
    if (!_paused) {
      _dispatchPaused();
    }
    super.onClose();
  }

  //
  // @mustCallSuper
  // @override
  // void didChangeAppLifecycleState(AppLifecycleState state) {
  //   Log.d("[$runtimeType] didChangeAppLifecycleState:$state");
  //   if (state == AppLifecycleState.resumed) {
  //     lifecycleState.addEx(LifecycleState.resumed);
  //   } else {
  //     lifecycleState.addEx(LifecycleState.paused);
  //   }
  // }

  void _dispatchLifecycleAction() async {
    if (!_dispatching) {
      _dispatching = true;
      while (actions.isNotEmpty && isResumed) {
        final action = actions.removeFirst();
        try {
          final result = await action.invoke();
          if (result == LifecycleActionResult.suspend) {
            actions.addLast(action);
          }
        } catch (error, stacktrace) {
          Log.e("_dispatchLifecycleAction invoke error! $error $stacktrace");
        }
      }
      _dispatching = false;
    }
  }

  void _dispatchResumed() {
    _paused = false;
    onResumed();

    _dispatchLifecycleAction();
  }

  void _dispatchPaused() {
    _paused = true;
    onPaused();
  }

  void onResumed() {
    Log.d("LifecycleChanged: $runtimeType onResumed!");
  }

  void onPaused() {
    Log.d("LifecycleChanged: $runtimeType onPaused!");
  }

  void onAppBackground() {
    Log.d("LifecycleChanged: $runtimeType onAppBackground!");
  }

  void onAppForeground() {
    Log.d("LifecycleChanged: $runtimeType onAppForeground!");
  }
}
