package io.netfoundry.billing.external.netsuite.client;

import static io.netfoundry.billing.util.ClientUtils.tryWithCapture;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netfoundry.billing.external.netsuite.domain.BillingAccount;
import io.netfoundry.billing.external.netsuite.domain.Customer;
import io.netfoundry.billing.external.netsuite.domain.NetSuiteCancelledSubscription;
import io.netfoundry.billing.external.netsuite.domain.NetSuiteSusbscriptionUsage;
import io.netfoundry.billing.external.netsuite.domain.PricingTier;
import io.netfoundry.billing.external.netsuite.domain.PricingTierResponse;
import io.netfoundry.billing.external.netsuite.domain.SubscriptionPriceIntervalResponse;
import io.netfoundry.common.util.RestServiceClient;
import io.netfoundry.common.util.RestServiceClient.Capture;
import io.netfoundry.common.util.RestServiceClient.Command;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class NetSuiteApiClient {
  private static final String INVALID_TOKEN_ERROR_MSG = "Failed to obtain valid token";
  protected static final UriTemplate CUSTOMER_PAYMENT_PROFILE_BY_ID_URI = new UriTemplate("{host}/record/v1/customer/{id}?expandSubResources=true");
  protected static final UriTemplate UPDATE_CUSTOMER_PAYMENT_PROFILE_BY_ID_URI = new UriTemplate("{host}/record/v1/customer/{id}");
  protected static final UriTemplate CUSTOMER_BILLING_PROFILE_BY_ID_URI = new UriTemplate("{host}/record/v1/billingAccount/{id}?expandSubResources=true");
  protected static final UriTemplate SUBSCRIPTION_BY_ID_URI = new UriTemplate("{host}/record/v1/subscription/{id}?expandSubResources=true");
  protected static final UriTemplate SUITE_SQL_URI = new UriTemplate("{host}/query/v1/suiteql");
  protected static final UriTemplate POST_USAGE_URI = new UriTemplate("{host}/record/v1/usage");
  protected static final UriTemplate SUBSCRIPTION_PRICE_PLAN_URI = new UriTemplate("{host}/record/v1/priceplan/{id}?expandSubResources=true");

  private static final String FIND_INACTIVE_SUBSCRIPTION =
      "SELECT id,billingSubscriptionStatus FROM subscription WHERE billingSubscriptionStatus = 'TERMINATED' AND lastmodifieddate >= (SYSDATE)";
  private static final String FIND_PRICE_TIERS =
      "SELECT PP.ID, PT.FROMVAL, PT.VALUE FROM PRICEPLAN PP JOIN PRICETIERS PT ON PP.ID = PT.PRICEPLAN WHERE PP.ID IN (PP_IDS)";


  private final NetSuiteProperties netSuiteProperties;
  private final RestServiceClient restServiceClient;
  private final NetSuiteAuthService netSuiteAuthService;


  public NetSuiteApiClient(NetSuiteProperties netSuiteProperties, RestServiceClient restServiceClient, NetSuiteAuthService netSuiteAuthService) {
    this.netSuiteProperties = netSuiteProperties;
    this.restServiceClient = restServiceClient;
    this.netSuiteAuthService = netSuiteAuthService;
  }

  public Optional<SubscriptionPriceIntervalResponse> getSubscription(String subscriptionId) {
    Consumer<HttpHeaders> netsuiteAuth = headers -> {
      try {
        headers.setBearerAuth(netSuiteAuthService.getValidToken());
      } catch (Exception exception) {
        log.error(INVALID_TOKEN_ERROR_MSG, exception);

      }
    };
    final Function<Capture, SubscriptionPriceIntervalResponse> apiRequest =
        c -> restServiceClient.get(SUBSCRIPTION_BY_ID_URI.expand(netSuiteProperties.getApiBaseUrl(), subscriptionId),
            netsuiteAuth, new ParameterizedTypeReference<>() {}, c.andHush(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN));
    return Optional.ofNullable(Optional.ofNullable(tryWithCapture(apiRequest))
        .orElseGet(() -> {
          log.warn("NetSuite subscription not found for the subscriptionId: {}", subscriptionId);
          return null;
        }));

  }

  public Optional<PricingTierResponse> getSubscriptionPricingTier(String pricePlanId) {
    Consumer<HttpHeaders> netsuiteAuth = headers -> {
      try {
        headers.setBearerAuth(netSuiteAuthService.getValidToken());
      } catch (Exception exception) {
        log.error(INVALID_TOKEN_ERROR_MSG, exception);

      }
    };
    final Function<Capture, PricingTierResponse> apiRequest = c -> restServiceClient.get(SUBSCRIPTION_PRICE_PLAN_URI.expand(netSuiteProperties.getApiBaseUrl(), pricePlanId),
        netsuiteAuth, new ParameterizedTypeReference<>() {}, c.andHush(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN));
    return Optional.ofNullable(Optional.ofNullable(tryWithCapture(apiRequest))
        .orElseGet(() -> {
          log.warn("NetSuite subscription not found for the subscriptionId: {}", pricePlanId);
          return null;
        }));

  }

  public Optional<Customer> getCustomerPaymentProfile(String customerId) {
    Consumer<HttpHeaders> netsuiteAuth = headers -> {
      try {
        headers.setBearerAuth(netSuiteAuthService.getValidToken());
      } catch (Exception exception) {
        log.error(INVALID_TOKEN_ERROR_MSG, exception);

      }
    };
    final Function<Capture, Customer> apiRequest =
        c -> restServiceClient.get(CUSTOMER_PAYMENT_PROFILE_BY_ID_URI.expand(netSuiteProperties.getApiBaseUrl(), customerId), netsuiteAuth,
            new ParameterizedTypeReference<>() {},
            c.andHush(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN));
    return Optional.ofNullable(Optional.ofNullable(tryWithCapture(apiRequest))
        .orElseGet(() -> {
          log.warn("NetSuite billing account not found for the billingAccountId: {}", customerId);
          return null;
        }));
  }

  public void updateCustomerPaymentProfile(String paymentProfileId, Customer customer) {
    Consumer<HttpHeaders> netsuiteAuth = headers -> {
      try {
        headers.setBearerAuth(netSuiteAuthService.getValidToken());
      } catch (Exception exception) {
        log.error(INVALID_TOKEN_ERROR_MSG, exception);

      }
    };
    final Command customerPatchCommand = Command.of(customer);

    final Function<Capture, Customer> apiRequest =
        c -> restServiceClient.patch(customerPatchCommand, UPDATE_CUSTOMER_PAYMENT_PROFILE_BY_ID_URI.expand(netSuiteProperties.getApiBaseUrl(), paymentProfileId), netsuiteAuth,
            new ParameterizedTypeReference<>() {},
            c.andHush(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN));
    tryWithCapture(apiRequest);
  }

  public BillingAccount getBillingProfile(String billingAccountId) {
    Consumer<HttpHeaders> netsuiteAuth = headers -> {
      try {
        headers.setBearerAuth(netSuiteAuthService.getValidToken());
      } catch (Exception exception) {
        log.error(INVALID_TOKEN_ERROR_MSG, exception);

      }
    };
    final Function<Capture, BillingAccount> apiRequest =
        c -> restServiceClient.get(CUSTOMER_BILLING_PROFILE_BY_ID_URI.expand(netSuiteProperties.getApiBaseUrl(), billingAccountId), netsuiteAuth,
            new ParameterizedTypeReference<>() {},
            c.andHush(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN));
    return Optional.ofNullable(tryWithCapture(apiRequest))
        .orElseGet(() -> {
          log.warn("NetSuite billing account not found for the billingAccountId: {}", billingAccountId);
          return new BillingAccount();
        });

  }

  public void submitSubscriptionUsage(NetSuiteSusbscriptionUsage monthlyUsageRequest) {
    Command body = Command.of().set("usageSubscription", monthlyUsageRequest.getUsageSubscription()).set("usageSubscriptionLine", monthlyUsageRequest.getUsageSubscriptionLine())
        .set("usagedate", monthlyUsageRequest.getUsageDate()).set("usagequantity", monthlyUsageRequest.getUsageQuantity());
    Consumer<HttpHeaders> netsuiteAuth = headers -> {
      try {
        headers.setBearerAuth(netSuiteAuthService.getValidToken());
      } catch (Exception exception) {
        log.error(INVALID_TOKEN_ERROR_MSG, exception);
      }
    };
    final Function<Capture, Void> apiRequest = c -> restServiceClient.post(body, POST_USAGE_URI.expand(netSuiteProperties.getApiBaseUrl()), netsuiteAuth,
        ParameterizedTypeReference.forType(JsonNode.class), c.andHush(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN));
    tryWithCapture(apiRequest);
  }

  public NetSuiteCancelledSubscription findCancelledSubscription() {
    Command body = Command.of().set("q", FIND_INACTIVE_SUBSCRIPTION);
    Consumer<HttpHeaders> netsuiteAuth = headers -> {
      try {
        headers.setBearerAuth(netSuiteAuthService.getValidToken());
        headers.set("Prefer", "transient");
      } catch (Exception exception) {
        log.error(INVALID_TOKEN_ERROR_MSG, exception);
      }
    };
    final Function<Capture, NetSuiteCancelledSubscription> apiRequest = c -> restServiceClient.post(body, SUITE_SQL_URI.expand(netSuiteProperties.getApiBaseUrl()), netsuiteAuth,
        new ParameterizedTypeReference<>() {}, c.andHush(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN));
    return tryWithCapture(apiRequest);
  }

  public PricingTier findSubscriptionPricePlanTier(String pricePlanId) {
    Command body = Command.of().set("q", FIND_PRICE_TIERS.replaceAll("PP_IDS", pricePlanId));
    Consumer<HttpHeaders> netsuiteAuth = headers -> {
      try {
        headers.setBearerAuth(netSuiteAuthService.getValidToken());
        headers.set("Prefer", "transient");
      } catch (Exception exception) {
        log.error(INVALID_TOKEN_ERROR_MSG, exception);
      }
    };
    final Function<Capture, PricingTier> apiRequest = c -> restServiceClient.post(body, SUITE_SQL_URI.expand(netSuiteProperties.getApiBaseUrl()), netsuiteAuth,
        new ParameterizedTypeReference<>() {}, c.andHush(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN));
    return tryWithCapture(apiRequest);
  }

  public static void main(String args[]) throws JsonProcessingException {

    RestTemplate restTemplate = new RestTemplate();



    Consumer<HttpHeaders> netsuiteAuth = headers -> {
      try {
        headers.setBearerAuth(
            "");

      } catch (Exception exception) {
        log.error(INVALID_TOKEN_ERROR_MSG, exception);
      }
    };

    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization",
        "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik5qUTVOekpCUTBJMk5USkZRMFZDTnpWRFJETXlRamxEUmtRek9EbERSa0pDTjBNek5VSTROUSJ9.eyJodHRwczovL25ldGZvdW5kcnkuaW8vdGVuYW50L2xhYmVsIjoiTkZBRE1JTiIsImh0dHBzOi8vbmV0Zm91bmRyeS5pby9hdXRoMC9jb25uZWN0aW9uSWQiOiJnb29nbGUtb2F1dGgyIiwiaXNzIjoiaHR0cHM6Ly9uZXRmb3VuZHJ5LXN0YWdpbmcuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTE2NTQ5OTc3ODc4MzA1MTMzMjY5IiwiYXVkIjpbImh0dHBzOi8vZ2F0ZXdheS5zdGFnaW5nLm5ldGZvdW5kcnkuaW8vIiwiaHR0cHM6Ly9uZXRmb3VuZHJ5LXN0YWdpbmcuYXV0aDAuY29tL3VzZXJpbmZvIl0sImlhdCI6MTc2NTUyMzkxMiwiZXhwIjoxNzY1NjEwMzEyLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwiYXpwIjoieWYxSE9MUGFWQzEyTU1VTnBtT3RvZjZTQktqV2cwa3AifQ.bqqwfB_qJS4i1MlJc5aP_KN7AqJJyolElPdHAeLrOO1a8awgFsp0tXUsDRGPdqi4SafM9nHQKKsAB-ejhG_t-wfOjxnqGT5TvW26GgVg4muWm6I7sa3ONw3Kh1zx46FguvilA5rsNCmefvCY9qqRWugAHIS9Z2fjG18F4HVrQfqSSuIWQmyEfQj4FEXmHJcCBxXqqPdrtXX9W8cgsRkVOSI-3mep6y7Gtktc72Fof_XanOa4k_LCZR-nYo449CoTcLEcb9GuCwtRq3l7gKZwf78kYnfQA5uefpgifiZY6RWW2nXifwmOGCN0SOE-PEu28aqm6gMAXk59-gmiOAsgWA");
    HttpEntity<?> entity = new HttpEntity<>(headers);

    // https://​gateway.staging.netfoundry.io/reporting/v1/template-search/ncevents_endpoint_last_seen_tmpl?indexName={indexName}&networkGroupId={networkGroupId}&networkId={networkId}&gte={gte}&lte={lte}&endpointId={endpointId}&size={size}
    UriTemplate URL = new UriTemplate(
        "https://gateway.staging.netfoundry.io/reporting/v1/template-search/ncevents_endpoint_last_seen_tmpl?indexName={indexName}&networkGroupId={networkGroupId}&networkId={networkId}&gte={gte}&lte={lte}&endpointId={endpointId}&size={size}");
    Map<String, Object> params = new HashMap<>();
    params.put("indexName", "ncevents");
    params.put("networkGroupId", "fa080396-8f02-4596-9bf4-d16e1686b4b0");
    params.put("networkId", "951eaaef-9d60-4db2-8401-c29dffcee637");
    params.put("gte", "now-90d");
    params.put("lte", "now");
    params.put("endpointId", "96d90268-c9bb-4075-b2e8-5cbcc9662a86");
    params.put("size", 0);

    URI expand = URL.expand(params);
    // 3. Call API and return JSON as Map
    ResponseEntity<Map<String, Object>> response =
        restTemplate.exchange(
            expand,
            HttpMethod.GET,
            entity,
            new ParameterizedTypeReference<Map<String, Object>>() {});

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(response.getBody());


    System.out.println(json);


  }

}
