import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:hl_lieferservice/dto/delivery_response.dart';
import 'package:hl_lieferservice/dto/delivery_update.dart';
import 'package:hl_lieferservice/dto/delivery_update_response.dart';
import 'package:hl_lieferservice/dto/payment.dart';
import 'package:hl_lieferservice/dto/payments.dart';
import 'package:hl_lieferservice/dto/set_article_amount_request.dart';
import 'package:hl_lieferservice/dto/set_article_amount_response.dart';
import 'package:hl_lieferservice/model/car.dart';
import 'package:hl_lieferservice/model/delivery.dart';
import 'package:hl_lieferservice/model/tour.dart';
import 'package:hl_lieferservice/util.dart';
import 'package:http/http.dart';

import '../../../dto/basic_response.dart';
import '../../../dto/discount_add_response.dart';
import '../../../dto/discount_remove_response.dart';
import '../../../dto/discount_update_response.dart';
import '../../../dto/scan_response.dart';
import '../../authentication/exceptions.dart';

class TourService {
  TourService();

  Future<void> updateDelivery(Delivery delivery) async {
    try {
      var headers = {"Content-Type": "application/json"};
      headers.addAll(getSessionOrThrow());

      debugPrint(getSessionOrThrow().toString());
      debugPrint(delivery.state.toString());
      debugPrint(jsonEncode(DeliveryUpdateDTO.fromEntity(delivery).toJson()));

      var response = await post(
        urlBuilder("_web_updateDelivery"),
        headers: headers,
        body: jsonEncode(DeliveryUpdateDTO.fromEntity(delivery).toJson()),
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      debugPrint("BODY: ${response.body}");
      Map<String, dynamic> responseJson = jsonDecode(response.body);
      DeliveryUpdateResponseDTO responseDto =
          DeliveryUpdateResponseDTO.fromJson(responseJson);

      if (responseDto.code == "200") {
        return;
      }

      debugPrint("ERROR UPDATING:");
      debugPrint(responseDto.message);
    } catch (e, st) {
      debugPrint("ERROR WHILE UPDATING DELIVERY");
      debugPrint("$e");
      debugPrint(st.toString());

      rethrow;
    }
  }

  Future<void> assignCar(String deliveryId, String carId) async {
    try {
      var headers = {"Content-Type": "application/json"};
      headers.addAll(getSessionOrThrow());

      var response = await post(
        urlBuilder("_web_updateDelivery"),
        headers: headers,
        body: jsonEncode({"delivery_id": deliveryId, "car_id": carId}),
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      Map<String, dynamic> responseJson = jsonDecode(response.body);
      DeliveryUpdateResponseDTO responseDto =
          DeliveryUpdateResponseDTO.fromJson(responseJson);

      if (responseDto.code == "200") {
        return;
      }

      debugPrint("ERROR UPDATING:");
      debugPrint(responseDto.message);
    } catch (e, st) {
      debugPrint("ERROR WHILE UPDATING DELIVERY");
      debugPrint("$e");
      debugPrint(st.toString());

      rethrow;
    }
  }

  /// List all available deliveries for today.

  Future<Tour> getTourOfToday(String userId) async {
    try {
      var response = await post(
        urlBuilder("_web_getDeliveries"),
        headers: getSessionOrThrow(),
        body: {"driver_id": userId, "date": getTodayDate()},
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      DeliveryResponseDTO responseDto = DeliveryResponseDTO.fromJson(
        jsonDecode(response.body),
      );

      return Tour(
        discountArticleNumber: responseDto.discountArticleNumber,
        date: DateTime.now(),
        deliveries: responseDto.deliveries.map(Delivery.fromDTO).toList(),
        paymentMethods: [],
        driver: Driver(
          cars:
              responseDto.driver.cars
                  .map(
                    (carDto) =>
                        Car(id: int.parse(carDto.id), plate: carDto.plate),
                  )
                  .toList(),
          teamNumber: int.parse(responseDto.driver.id),
          name: responseDto.driver.name,
          salutation: responseDto.driver.salutation,
        ),
      );
    } catch (e, stacktrace) {
      debugPrint(e.toString());
      debugPrint(stacktrace.toString());
      debugPrint("RANDOM EXCEPTION!");

      rethrow;
    }
  }

  Future<List<PaymentMethodDTO>> getPaymentMethods() async {
    try {
      var response = await post(
        urlBuilder("_web_getPaymentMethods"),
        headers: getSessionOrThrow(),
        body: {},
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      Map<String, dynamic> responseJson = jsonDecode(response.body);
      PaymentMethodListDTO responseDto = PaymentMethodListDTO.fromJson(
        responseJson,
      );

      return responseDto.paymentMethods;
    } catch (e, st) {
      debugPrint("ERROR while retrieving allowed payment methods");
      debugPrint(e.toString());
      debugPrint(st.toString());

      rethrow;
    }
  }

  Future<String?> unscanArticle(
    String internalId,
    int amount,
    String reason,
  ) async {
    try {
      var response = await post(
        urlBuilder("_web_unscanArticle"),
        headers: getSessionOrThrow(),
        body: {
          "article_id": internalId,
          "amount": amount.toString(),
          "reason": reason,
        },
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      Map<String, dynamic> responseJson = jsonDecode(response.body);
      ScanResponseDTO responseDto = ScanResponseDTO.fromJson(responseJson);

      if (responseDto.succeeded == true) {
        return responseDto.noteId;
      } else {
        throw responseDto.message;
      }
    } catch (e, st) {
      debugPrint("ERROR WHILE REVERTING THE SCAN OF ARTICLE $internalId");
      debugPrint(e.toString());
      debugPrint(st.toString());

      rethrow;
    }
  }

  Future<void> resetScannedArticleAmount(String receiptRowId) async {
    try {
      var response = await post(
        urlBuilder("_web_unscanArticleReset"),
        headers: getSessionOrThrow(),
        body: {"receipt_row_id": receiptRowId},
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      Map<String, dynamic> responseJson = jsonDecode(response.body);
      BasicResponseDTO responseDto = BasicResponseDTO.fromJson(responseJson);

      if (responseDto.succeeded == true) {
        return;
      } else {
        throw responseDto.message;
      }
    } catch (e, st) {
      debugPrint("ERROR WHILE REVERTING THE UNSCAN OF ARTICLE $receiptRowId");
      debugPrint(e.toString());
      debugPrint(st.toString());

      rethrow;
    }
  }

  Future<DiscountAddResponseDTO> addDiscount(
    String deliveryId,
    int discount,
    String note,
  ) async {
    try {
      var response = await post(
        urlBuilder("_web_addDiscount"),
        headers: getSessionOrThrow(),
        body: {
          "delivery_id": deliveryId,
          "discount": discount.toString(),
          "note": note,
        },
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      Map<String, dynamic> responseJson = jsonDecode(response.body);

      // let it throw, if the values are invalid
      return DiscountAddResponseDTO.fromJson(responseJson);
    } catch (e, st) {
      debugPrint("ERROR while adding discount");
      debugPrint(e.toString());
      debugPrint(st.toString());

      rethrow;
    }
  }

  Future<BasicResponseDTO> finishDelivery(String deliveryId) async {
    try {
      // ISO-8601 mit T-Separator: sprachunabhaengig fuer SQL-Server datetime.
      // ('yyyy-MM-dd HH:mm:ss' OHNE T wird unter DATEFORMAT=DMY (DE) als YDM
      //  geparst und schlaegt fuer Tag > 12 fehl.)
      // ERPframe arbeitet mit lokaler Zeit -> bewusst keine UTC-Konvertierung.
      final String deliveredAt = DateFormat(
        "yyyy-MM-dd'T'HH:mm:ss",
      ).format(DateTime.now());

      var headers = {"Content-Type": "application/json"};
      headers.addAll(getSessionOrThrow());

      var response = await post(
        urlBuilder("_web_finishDelivery"),
        headers: headers,
        body: jsonEncode({
          "delivery_id": deliveryId,
          "delivered_at": deliveredAt,
        }),
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      debugPrint("BODY: ${response.body}");
      Map<String, dynamic> responseJson = jsonDecode(response.body);

      // let it throw, if the values are invalid
      return BasicResponseDTO.fromJson(responseJson);
    } catch (e, st) {
      debugPrint("ERROR while adding discount");
      debugPrint(e.toString());
      debugPrint(st.toString());

      rethrow;
    }
  }

  Future<SetArticleAmountResponseDTO> setArticleAmount(
    String deliveryId,
    String articleId,
    int amount,
    String? reason,
  ) async {
    try {
      var response = await post(
        urlBuilder("_web_setArticleAmount"),
        headers: {...getSessionOrThrow(), "Content-Type": "application/json"},
        body: jsonEncode(
          SetArticleAmountRequestDTO(
            articleId: articleId,
            deliveryId: deliveryId,
            amount: amount,
            reason: reason,
          ),
        ),
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      debugPrint("BODY: ${response.body}");

      Map<String, dynamic> responseJson = jsonDecode(response.body);
      // let it throw, if the values are invalid
      SetArticleAmountResponseDTO responseDto =
          SetArticleAmountResponseDTO.fromJson(responseJson);

      if (!responseDto.succeeded) {
        throw responseDto.message;
      } else {
        return responseDto;
      }
    } catch (e, st) {
      debugPrint(e.toString());
      debugPrint(st.toString());

      rethrow;
    }
  }

  Future<DiscountRemoveResponseDTO> removeDiscount(String deliveryId) async {
    try {
      var response = await post(
        urlBuilder("_web_removeDiscount"),
        headers: getSessionOrThrow(),
        body: {"delivery_id": deliveryId},
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      Map<String, dynamic> responseJson = jsonDecode(response.body);

      // let it throw, if the values are invalid
      return DiscountRemoveResponseDTO.fromJson(responseJson);
    } catch (e, st) {
      debugPrint("ERROR while removing discount");
      debugPrint(e.toString());
      debugPrint(st.toString());

      rethrow;
    }
  }

  Future<DiscountUpdateResponseDTO> updateDiscount(
    String deliveryId,
    String? note,
    int? discount,
  ) async {
    try {
      var response = await post(
        urlBuilder("_web_updateDiscount"),
        headers: getSessionOrThrow(),
        body: {"delivery_id": deliveryId, "discount": discount, "note": note},
      );

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      Map<String, dynamic> responseJson = jsonDecode(response.body);

      // let it throw, if the values are invalid
      return DiscountUpdateResponseDTO.fromJson(responseJson);
    } catch (e, st) {
      debugPrint("ERROR while retrieving allowed payment methods");
      debugPrint(e.toString());
      debugPrint(st.toString());

      rethrow;
    }
  }

  Future<void> scanArticle(String internalId) async {
    try {
      var response = await post(
        urlBuilder("_web_scanArticle"),
        headers: getSessionOrThrow(),
        body: {"internal_id": internalId},
      );

      debugPrint(jsonEncode({"internal_id": internalId}));

      if (response.statusCode == HttpStatus.unauthorized) {
        throw UserUnauthorized();
      }

      Map<String, dynamic> responseJson = jsonDecode(response.body);
      debugPrint(responseJson.toString());
      ScanResponseDTO responseDto = ScanResponseDTO.fromJson(responseJson);

      if (responseDto.succeeded == true) {
        return;
      } else {
        debugPrint("ERROR: ${responseDto.message}");
        throw responseDto.message;
      }
    } catch (e) {
      rethrow;
    }
  }
}
