import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:qpay/http/http.dart';
import 'package:qpay/models/api_response.dart';
import 'package:qpay/screens/home/home_controller.dart' as hc;
import 'package:qpay/screens/transaction_controller.dart';
import 'package:qpay/screens/transaction_controller.dart' as tc;

part 'pay_controller.freezed.dart';
part 'pay_controller.g.dart';

class PayScreenModel {
  List<BillProduct> products = [];
  List<PaymentProcessor> paymentProcessors = [];
  hc.BillProvider? selectedProvider;
  BillProduct? selectedProduct;
  PaymentProcessor? selectedPaymentProcessor;
  String region = '';
  bool isLoading = false;
  bool isLoadingProducts = false;
  String? errorMessage;
  String? status;
}

@freezed
abstract class PaymentProcessor with _$PaymentProcessor {
  const factory PaymentProcessor({
    required String name,
    required String description,
    required String image,
    required String accountFieldName,
    required String accountFieldLabel,
    required String label,
    required String currency,
    required String authType,
  }) = _PaymentProcessor;

  factory PaymentProcessor.fromJson(Map<String, dynamic> json) =>
      _$PaymentProcessorFromJson(json);
}

@freezed
abstract class BillProduct with _$BillProduct {
  const factory BillProduct({
    required String uid,
    required String name,
    required String description,
    required String displayName,
    required double defaultAmount,
    required bool requiresAmount,
  }) = _BillProduct;

  factory BillProduct.fromJson(Map<String, dynamic> json) =>
      _$BillProductFromJson(json);
}

class PayController extends ChangeNotifier {
  final PayScreenModel model = PayScreenModel();
  final Http http = Http();
  late SharedPreferences prefs;
  late TransactionController transactionController;
  BuildContext context;

  PayController(this.context) {
    transactionController = Provider.of<TransactionController>(
      context,
      listen: false,
    );
    model.selectedProvider = transactionController.model.selectedProvider;
    model.selectedProduct = transactionController.model.selectedProduct;
  }

  // formData can come from a repeat transaction
  // otherwise build it from elements of the normal flow
  Future<ApiResponse<Map<String, dynamic>>> doTransaction([
    tc.FormData? formData,
  ]) async {
    try {
      model.isLoading = true;
      notifyListeners();

      prefs = await SharedPreferences.getInstance();

      transactionController.model.formData =
          formData ??
          transactionController.model.formData.copyWith(
            type: "CONFIRM",
            billClientId: model.selectedProvider?.clientId ?? '',
            debitRef: 'Velocity',
            debitCurrency: transactionController.model.currency,
            creditAccount: transactionController.model.formData.creditAccount,
            creditPhone: transactionController.model.formData.creditPhone,
            billName: model.selectedProvider?.name ?? '',
            paymentProcessorLabel: model.selectedPaymentProcessor?.label ?? '',
            paymentProcessorName: model.selectedPaymentProcessor?.name ?? '',
            paymentProcessorImage: model.selectedPaymentProcessor?.image ?? '',
            providerImage: model.selectedProvider?.image ?? '',
            providerLabel: model.selectedProvider?.label ?? '',
            workspaceId: prefs.getString("workspaceId") ?? '',
            userId: prefs.getString("userId") ?? '',
            creditName: transactionController.model.formData.creditName,
            creditEmail: transactionController.model.formData.creditEmail,
            productUid: model.selectedProduct?.uid,
            billProductName: model.selectedProduct?.name,
            region: model.region,
          );

      dynamic workflowResponse = await http.post(
        '/public/transaction',
        transactionController.model.formData,
      );
      logger.i(workflowResponse.toString());

      dynamic response = workflowResponse['body'];

      model.isLoading = false;
      notifyListeners();

      if (response['status'] == 'SUCCESS') {
        model.status = response['status'];
        transactionController.updateConfirmationData(response);
        return ApiResponse.success(Map<String, dynamic>.from(response));
      } else {
        model.status = response['status'];
        model.errorMessage =
            response['errorMessage'] ??
            "Problem with the transaction. Please try again or contact support";
        return ApiResponse.failure(model.errorMessage!);
      }
    } on DioException catch (e, s) {
      if (kDebugMode) {
        logger.e(e);
        logger.e(s);
      }
      model.isLoading = false;
      notifyListeners();
      return ApiResponse.failure(
        "Network error. Please try again or contact support",
      );
    } catch (e, s) {
      if (kDebugMode) {
        logger.e(e);
        logger.e(s);
      }
      model.isLoading = false;
      notifyListeners();
      return ApiResponse.failure(
        "Problem processing that transaction. Please try again or contact support",
      );
    }
  }

  Future<ApiResponse<List<BillProduct>>> getProducts(String providerId) async {
    try {
      model.isLoadingProducts = true;
      notifyListeners();

      List<dynamic> response = await http.get(
        '/public/providers/$providerId/products',
      );
      model.products = response.map((e) => BillProduct.fromJson(e)).toList();

      model.isLoadingProducts = false;
      notifyListeners();
      return ApiResponse.success(model.products);
    } catch (e) {
      logger.e(e);
      model.isLoadingProducts = false;
      notifyListeners();
      return ApiResponse.failure(
        "Problem fetching products, are you connected to the internet?",
      );
    }
  }

  Future<ApiResponse<List<PaymentProcessor>>> getPaymentProcessors(
    String currency,
  ) async {
    try {
      model.isLoading = true;
      model.paymentProcessors = getFakePaymentProcessors();
      notifyListeners();

      final response = await http.get(
        '/public/payment-processors?currency=$currency',
      );
      final List<dynamic> content = response is List
          ? response
          : (response['content'] ?? []);
      model.paymentProcessors = content
          .map((e) => PaymentProcessor.fromJson(e as Map<String, dynamic>))
          .toList();

      model.isLoading = false;
      notifyListeners();
      return ApiResponse.success(model.paymentProcessors);
    } catch (e) {
      logger.e(e);
      model.isLoading = false;
      notifyListeners();
      return ApiResponse.failure(
        "Problem fetching payment processors, are you connected to the internet?",
      );
    }
  }

  void updateAdditionalRecipientDetails(String name, String email) {
    transactionController.model.formData = transactionController.model.formData
        .copyWith(creditName: name, creditEmail: email);
    notifyListeners();
  }

  void updatePhone(String phone) {
    transactionController.model.formData = transactionController.model.formData
        .copyWith(debitPhone: phone);
    notifyListeners();
  }

  void updateAmount(String amount) {
    transactionController.model.formData = transactionController.model.formData
        .copyWith(amount: amount);
    notifyListeners();
  }

  void updateSelectedProduct(BillProduct product) {
    model.selectedProduct = product;
    notifyListeners();
  }

  void updateSelectedPaymentProcessor(PaymentProcessor processor) {
    model.selectedPaymentProcessor = processor;
    notifyListeners();
  }

  List<PaymentProcessor> getFakePaymentProcessors() {
    return [
      PaymentProcessor(
        name: "EcoCash",
        description: "Pay using your mobile wallet.",
        image: "ecocash.png",
        accountFieldName: "phoneNumber",
        accountFieldLabel: "EcoCash Phone Number",
        label: "ECOCASH",
        authType: "REMOTE",
        currency: "USD",
      ),
    ];
  }

  List<BillProduct> getFakeProducts() {
    return [
      BillProduct(
        uid: '1',
        name: 'Product 1',
        description: 'Description 1',
        displayName: 'Product 1',
        defaultAmount: 100,
        requiresAmount: true,
      ),
    ];
  }
}
