import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import '../../about/external/data/GroundedError.dart';
import '../../about/external/data/Severity.dart';
import '../../about/internal/application/NavigatorType.dart';
import '../../about/internal/application/NotificationType.dart';
import '../../about/internal/application/TextType.dart';
import '../../about/internal/file/FileStorage.dart';
import '../../comms/Comms.dart';
import '../../configs/Navigator.dart';
import '../../designs/Component.dart';
import '../../designs/buttons/Buttons.dart';
import '../../designs/text/Text.dart';
import '../../informatics/AppDataManager.dart';
import '../../informatics/DataManager.dart';
import '../../memory/InternalMemory.dart';
import '../../utils/Colors.dart';
import '../system/sessionexpired/SessionExpired.dart';
import '../system/updateme/UpdateMe.dart';

/// Everything cross-cutting lives here: the single data gateway, the loading,
/// network and error overlays, and the error decision tree that lets the
/// backend steer the client.
class ParentViewModel extends ChangeNotifier {
  OverlayEntry? loadingEntry;

  OverlayEntry? networkEntry;

  OverlayEntry? errorEntry;

  late DataManager dataManager;

  BuildContext context;

  OverlayState? overlayState;

  ParentViewModel(this.context) {
    overlayState = Overlay.of(context);
    dataManager =
        AppDataManager(InternalMemory(), Comms(InternalMemory()), FileStorage());
  }

  DataManager getDataManager() {
    return dataManager;
  }

  // ── Loading ───────────────────────────────────────────────────────────────

  void showLoading(String loadingText) async {
    if (loadingEntry == null) {
      _hideKeyboard();
      loadingEntry = OverlayEntry(builder: (context) {
        return Scaffold(
          backgroundColor: colorPrimaryDark,
          body: SafeArea(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 28),
              child: Column(
                mainAxisSize: MainAxisSize.max,
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Align(
                    alignment: Alignment.centerLeft,
                    child: text(
                      "GROUNDED",
                      10,
                      TextType.Bold,
                      color: colorWhite.withValues(alpha: 0.45),
                      letterSpacing: 2.0,
                    ),
                  ),
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      SizedBox(
                        width: 46,
                        height: 46,
                        child: CircularProgressIndicator(
                          strokeWidth: 2.5,
                          backgroundColor: colorWhite.withValues(alpha: 0.12),
                          color: colorWhite,
                        ),
                      ),
                      const SizedBox(height: 32),
                      text(
                        loadingText,
                        30,
                        TextType.Light,
                        color: colorWhite,
                        align: TextAlign.center,
                        height: 1.15,
                      ),
                    ],
                  ),
                  text(
                    "Keep your connection active",
                    12,
                    TextType.Regular,
                    color: colorWhite.withValues(alpha: 0.45),
                  ),
                ],
              ),
            ),
          ),
        );
      });

      overlayState?.insert(loadingEntry!);
      notifyListeners();
    }
  }

  closeLoading() {
    if (loadingEntry != null) {
      loadingEntry?.remove();
      loadingEntry = null;
      notifyListeners();
    }
  }

  // ── Network ───────────────────────────────────────────────────────────────

  void noNetwork(Function() actions) async {
    if (networkEntry == null) {
      _hideKeyboard();
      networkEntry = OverlayEntry(builder: (context) {
        return Scaffold(
          backgroundColor: colorPrimaryLight,
          body: SafeArea(
            child: Center(
              child: SingleChildScrollView(
                padding: const EdgeInsets.all(28),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Container(
                      width: 64,
                      height: 64,
                      decoration: BoxDecoration(
                        color: colorPrimaryDark,
                        borderRadius: BorderRadius.circular(18),
                      ),
                      child: Icon(CupertinoIcons.wifi_slash,
                          size: 26, color: colorWhite),
                    ),
                    const SizedBox(height: 28),
                    text("CONNECTION", 10, TextType.Bold,
                        color: colorGrey2, letterSpacing: 1.2),
                    const SizedBox(height: 10),
                    text("You are offline.", 34, TextType.Light,
                        color: colorPrimaryDark, height: 1.15),
                    const SizedBox(height: 12),
                    text(
                      "Reconnect and we will pick up where you left off. Nothing has been lost.",
                      14,
                      TextType.Regular,
                      color: colorGrey2,
                      height: 1.5,
                    ),
                    const SizedBox(height: 32),
                    SizedBox(
                      width: double.infinity,
                      child: roundedCornerButton(
                        "Try again",
                        actions,
                        icon: CupertinoIcons.refresh,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      });

      overlayState?.insert(networkEntry!);
      notifyListeners();
    }
  }

  closeNetwork() {
    if (networkEntry != null) {
      networkEntry?.remove();
      networkEntry = null;
      notifyListeners();
    }
  }

  /// Guard every network call with this. Returns false and raises the offline
  /// overlay wired to [actions] when there is nothing to talk to.
  Future<bool> hasNetwork(Function() actions) async {
    closeLoading();

    List<ConnectivityResult> resultList =
        await (Connectivity().checkConnectivity());

    ConnectivityResult connectivityResult = ConnectivityResult.none;

    if (resultList.isNotEmpty) {
      connectivityResult = resultList.first;
    }

    if (connectivityResult == ConnectivityResult.mobile ||
        connectivityResult == ConnectivityResult.wifi ||
        connectivityResult == ConnectivityResult.ethernet) {
      closeNetwork();
      return true;
    }

    noNetwork(actions);
    return false;
  }

  // ── Errors ────────────────────────────────────────────────────────────────

  /// The standard catch handler. Session expiry and the server-directed
  /// redirects live here, so the backend can steer the client from any screen.
  handleError(Object? error, Function() actions, Function() closeAction,
      String buttonName) {
    closeLoading();

    if (error is DioException) {
      DioException dioError = error;

      if (error.type == DioExceptionType.connectionTimeout) {
        showError(
            actions,
            closeAction,
            GroundedError(
                code: 5000.01,
                message: "An error occurred while processing your request.",
                helper:
                    "Kindly ensure that you have a stable internet connection.",
                title: "Grounded Error",
                severity: Severity.message.name),
            buttonName);
      } else if (error.type == DioExceptionType.receiveTimeout) {
        showError(
            actions,
            closeAction,
            GroundedError(
                code: 5000.02,
                message: "An error occurred while processing your request.",
                helper:
                    "Kindly ensure that you have a stable internet connection.",
                title: "Grounded Error",
                severity: Severity.message.name),
            buttonName);
      } else if (dioError.response?.statusCode == 401) {
        sessionExpired();
      } else if (dioError.response?.statusCode == 403) {
        showError(
            actions,
            closeAction,
            GroundedError(
                code: 5100.00,
                message:
                    "A connection error occurred while processing your request. Usually a result of your network security blocking the request.",
                helper:
                    "Try using your mobile data or switching to a different network.",
                title: "Grounded Error",
                severity: Severity.message.name),
            buttonName);
      } else if (dioError.response?.statusCode == 413) {
        showError(actions, closeAction,
            getGroundedError(dioError.response?.data), buttonName);
      } else if (_isBusinessStatus(dioError.response?.statusCode)) {
        _handleBusinessError(dioError, actions, closeAction, buttonName);
      } else {
        showError(
            actions,
            closeAction,
            GroundedError(
                code: 5500.02,
                message: "An error occurred while processing your request.",
                helper:
                    "Kindly relaunch the application and try again. If the problem persists, contact us.",
                title: "Grounded Error",
                severity: Severity.message.name),
            buttonName);
      }
    } else if (error is Exception) {
      showError(
          actions,
          closeAction,
          GroundedError(
              code: 6000.01,
              message: "An error occurred while processing your request.",
              helper:
                  "Kindly relaunch the application and try again. If the problem persists, contact us.",
              title: "Grounded Error",
              severity: Severity.message.name),
          buttonName);
    } else if (error is int) {
      showError(
          actions,
          closeAction,
          GroundedError(
              code: 6000.02,
              message: "An error occurred while processing your request.",
              helper:
                  "Kindly relaunch or reinstall the application and retry below.",
              title: "Grounded Error",
              severity: Severity.message.name),
          buttonName);
    } else {
      showError(
          actions,
          closeAction,
          GroundedError(
              code: 8700.02,
              message: "An error occurred while processing your request.",
              helper:
                  "Kindly relaunch the application and try again. If the problem persists, contact us.",
              title: "Grounded Error",
              severity: Severity.message.name),
          buttonName);
    }
  }

  /// Statuses that carry an actionable error body the user should see. 500 is
  /// included for services not yet migrated to the per-status scheme.
  bool _isBusinessStatus(int? status) {
    return status == 400 ||
        status == 404 ||
        status == 409 ||
        status == 422 ||
        status == 429 ||
        status == 500;
  }

  /// Decodes the body and routes the app-flow control codes before falling
  /// back to showing the error.
  void _handleBusinessError(DioException dioError, Function() actions,
      Function() closeAction, String buttonName) {
    GroundedError error = getGroundedError(dioError.response?.data);

    if (error.code == 5000.901) {
      updateMe();
    } else {
      showError(actions, closeAction, error, buttonName);
    }
  }

  void showError(Function() actions, Function() closeActions,
      GroundedError error, String buttonText) async {
    if (errorEntry == null) {
      _hideKeyboard();
      errorEntry = OverlayEntry(builder: (context) {
        return Scaffold(
          backgroundColor: colorPrimaryLight,
          body: SafeArea(
            child: SingleChildScrollView(
              padding: const EdgeInsets.fromLTRB(24, 48, 24, 32),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  text("SYSTEM NOTICE", 10, TextType.Bold,
                      color: colorGrey2, letterSpacing: 1.2),
                  const SizedBox(height: 12),
                  text(error.title, 34, TextType.Light,
                      color: colorPrimaryDark, height: 1.15),
                  const SizedBox(height: 24),
                  card(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Row(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Container(
                              width: 44,
                              height: 44,
                              alignment: Alignment.center,
                              decoration: BoxDecoration(
                                color: colorStandingGroundedBg,
                                borderRadius: BorderRadius.circular(12),
                              ),
                              child: Icon(
                                CupertinoIcons.exclamationmark_triangle_fill,
                                size: 20,
                                color: colorStandingGrounded,
                              ),
                            ),
                            const SizedBox(width: 14),
                            Expanded(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                mainAxisSize: MainAxisSize.min,
                                children: [
                                  text("CODE", 9, TextType.Bold,
                                      color: colorGrey2, letterSpacing: 1.0),
                                  const SizedBox(height: 4),
                                  text(error.code.toString(), 17,
                                      TextType.Medium,
                                      color: colorPrimaryDark),
                                ],
                              ),
                            ),
                          ],
                        ),
                        hairline(
                            margin: const EdgeInsets.symmetric(vertical: 20)),
                        text("WHAT HAPPENED", 9, TextType.Bold,
                            color: colorGrey2, letterSpacing: 1.0),
                        const SizedBox(height: 6),
                        text(error.message, 14, TextType.Regular,
                            color: colorPrimaryDark, height: 1.5),
                        const SizedBox(height: 20),
                        text("WHAT TO DO", 9, TextType.Bold,
                            color: colorGrey2, letterSpacing: 1.0),
                        const SizedBox(height: 6),
                        text(error.helper, 14, TextType.Regular,
                            color: colorPrimaryDark, height: 1.5),
                      ],
                    ),
                  ),
                  const SizedBox(height: 28),
                  SizedBox(
                    width: double.infinity,
                    child: roundedCornerButton(
                      buttonText,
                      actions,
                      icon: CupertinoIcons.arrow_clockwise,
                    ),
                  ),
                  const SizedBox(height: 10),
                  SizedBox(
                    width: double.infinity,
                    child: outlinedActionButton("Close", () {
                      dismissError();
                      closeActions();
                    }),
                  ),
                ],
              ),
            ),
          ),
        );
      });

      overlayState?.insert(errorEntry!);
      notifyListeners();
    }
  }

  dismissError() {
    if (errorEntry != null) {
      errorEntry?.remove();
      errorEntry = null;
      notifyListeners();
    }
  }

  GroundedError getGroundedError(data) {
    GroundedError error;
    try {
      error = GroundedError.fromJson(data);
    } catch (e) {
      error = GroundedError(
          code: 900,
          message: "An error occurred while processing your request.",
          helper: "Kindly ensure that your internet connection is working.",
          title: "Grounded Error",
          severity: Severity.message.name);
    }

    return error;
  }

  // ── In-app notification ───────────────────────────────────────────────────

  showApplicationNotification(
    NotificationType type,
    String title,
    String description,
    bool enableDrag,
    bool barrierDismiss,
    VoidCallback? closeAction, {
    String? action,
    VoidCallback? positiveAction,
  }) async {
    Color actionColor;
    Color actionBgColor;
    IconData actionIconData;

    switch (type) {
      case NotificationType.success:
        actionIconData = Icons.check_circle_rounded;
        actionColor = colorPositive;
        actionBgColor = colorStandingGoodBg;
        break;
      case NotificationType.info:
        actionIconData = Icons.info_rounded;
        actionColor = colorPrimaryDark;
        actionBgColor = colorMuted;
        break;
      case NotificationType.warning:
        actionIconData = Icons.warning_amber_rounded;
        actionColor = colorStandingWarned;
        actionBgColor = colorStandingWarnedBg;
        break;
      case NotificationType.error:
        actionIconData = Icons.error_rounded;
        actionColor = colorStandingGrounded;
        actionBgColor = colorStandingGroundedBg;
        break;
    }

    final bool hasAction = action != null && positiveAction != null;

    await showModalBottomSheet(
      context: context,
      barrierColor: colorPrimaryDark.withValues(alpha: 0.6),
      backgroundColor: Colors.transparent,
      elevation: 0,
      enableDrag: enableDrag,
      isDismissible: barrierDismiss,
      isScrollControlled: true,
      builder: (BuildContext context) {
        return Container(
          decoration: BoxDecoration(
            color: colorSheetBackground,
            borderRadius: const BorderRadius.only(
              topLeft: Radius.circular(28),
              topRight: Radius.circular(28),
            ),
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Center(
                child: Container(
                  width: 36,
                  height: 4,
                  margin: const EdgeInsets.only(top: 12, bottom: 8),
                  decoration: BoxDecoration(
                    color: colorGrey.withValues(alpha: 0.35),
                    borderRadius: BorderRadius.circular(999),
                  ),
                ),
              ),
              Padding(
                padding: const EdgeInsets.fromLTRB(24, 12, 24, 32),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Container(
                          width: 52,
                          height: 52,
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: actionBgColor,
                            borderRadius: BorderRadius.circular(15),
                          ),
                          child: Icon(actionIconData,
                              size: 24, color: actionColor),
                        ),
                        iconButton(
                          Icon(Icons.close_rounded,
                              size: 17, color: colorGrey2),
                          () {
                            Navigator.pop(context);
                            closeAction?.call();
                          },
                          bordered: true,
                        ),
                      ],
                    ),
                    const SizedBox(height: 20),
                    text(type.name.toUpperCase(), 9, TextType.Bold,
                        color: actionColor, letterSpacing: 1.2),
                    const SizedBox(height: 10),
                    text(title, 26, TextType.Light,
                        color: colorPrimaryDark, height: 1.2),
                    const SizedBox(height: 12),
                    text(description, 14, TextType.Regular,
                        color: colorGrey2, height: 1.55),
                    const SizedBox(height: 28),
                    if (hasAction) ...[
                      SizedBox(
                        width: double.infinity,
                        child: roundedCornerButton(
                          action,
                          positiveAction,
                          background: actionColor,
                          icon: actionIconData,
                        ),
                      ),
                      const SizedBox(height: 10),
                    ],
                    Center(
                      child: textButton(
                        hasAction ? "Cancel" : "Dismiss",
                        () {
                          Navigator.pop(context);
                          closeAction?.call();
                        },
                        color: colorGrey2,
                        textSize: 13,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  // ── Server-directed navigation ────────────────────────────────────────────

  void sessionExpired() {
    GroundedNavigation().navigateToPage(
        NavigatorType.makeNewMain, const SessionExpired(), context);
  }

  void updateMe() {
    GroundedNavigation()
        .navigateToPage(NavigatorType.makeNewMain, const UpdateMe(), context);
  }

  _hideKeyboard() {
    SystemChannels.textInput.invokeMethod('TextInput.hide');
  }

  @override
  void dispose() {
    closeLoading();
    closeNetwork();
    dismissError();
    super.dispose();
  }
}
