package ru.micord.ervu.security.esia.service;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import ru.micord.ervu.security.esia.exception.EsiaException;
import ru.micord.ervu.security.esia.config.EsiaConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import ru.micord.ervu.security.esia.exception.EsiaException;
import ru.micord.ervu.security.esia.model.BrhsModel;
import ru.micord.ervu.security.esia.model.EmployeeModel;
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
import ru.micord.ervu.security.esia.model.EsiaHeader;
import ru.micord.ervu.security.esia.model.OrganizationModel;
import ru.micord.ervu.security.esia.model.PersonModel;

/**
 * @author Eduard Tihomirov
 */
@Service
public class UlDataServiceImpl implements UlDataService {

  @Autowired
  private EsiaConfig esiaConfig;

  @Autowired
  private ObjectMapper objectMapper;

  @Override
  public EmployeeModel getEmployeeModel(String accessToken) {
    EsiaAccessToken esiaAccessToken = readToken(accessToken);
    String scope = esiaAccessToken.getScope();
    String orgOid = scope.substring(scope.indexOf('=') + 1, scope.indexOf(' '));
    String prnsId = esiaAccessToken.getSbjId();
    EmployeeModel employeeModel = getEmplData(prnsId, orgOid, accessToken);
    return employeeModel;
  }

  @Override
  public EmployeeModel getChiefEmployeeModel(String accessToken) {
    try {
      EsiaAccessToken esiaAccessToken = readToken(accessToken);
      String scope = esiaAccessToken.getScope();
      String orgOid = scope.substring(scope.indexOf('=') + 1, scope.indexOf(' '));
      String url = esiaConfig.getEsiaBaseUri() + "rs/orgs/" + orgOid + "/emps?embed=(elements)";
      HttpRequest getReq = HttpRequest.newBuilder(URI.create(url))
          .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
          .header("Authorization", "Bearer ".concat(accessToken))
          .GET()
          .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout()))
          .build();
      HttpResponse<String> getResp = HttpClient.newBuilder()
          .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout()))
          .build()
          .send(getReq, HttpResponse.BodyHandlers.ofString());
      errorHandler(getResp);
      EmployeeModel[] employeeModels = objectMapper.readValue(
          objectMapper.readTree(getResp.body()).get("elements").toString(), EmployeeModel[].class);
      Optional<EmployeeModel> chief = Arrays.stream(employeeModels)
          .filter(employee -> employee.getChief().equals(true) && employee.getBlocked().equals(false))
          .findFirst();
      if (chief.isPresent()) {
        EmployeeModel chiefModel = chief.get();
        PersonModel personModel = getPersonData(chiefModel.getPrnOid(), accessToken);
        chiefModel.setPerson(personModel);
        return chiefModel;
      }
      return null;
    }
    catch (Exception e) {
      throw new EsiaException(e);
    }
  }

  @Override
  public OrganizationModel getOrganizationModel(String accessToken) {
    EsiaAccessToken esiaAccessToken = readToken(accessToken);
    String scope = esiaAccessToken.getScope();
    String orgOid = scope.substring(scope.indexOf('=') + 1, scope.indexOf(' '));
    OrganizationModel organizationModel = getOrgModel(orgOid, accessToken);
    return organizationModel;
  }

  private EmployeeModel getEmplData(String prnsId, String orgOid, String accessToken) {
    try {
      String url = esiaConfig.getEsiaBaseUri() + "rs/orgs/" + orgOid + "/emps/" + prnsId + "?embed=(elements.person)";
      HttpRequest getReq = HttpRequest.newBuilder(URI.create(url))
          .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
          .header("Authorization", "Bearer ".concat(accessToken))
          .GET()
          .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout()))
          .build();
      HttpResponse<String> getResp = HttpClient.newBuilder()
          .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout()))
          .build()
          .send(getReq, HttpResponse.BodyHandlers.ofString());
      errorHandler(getResp);
      return objectMapper.readValue(getResp.body(), EmployeeModel.class);
    }
    catch (Exception e) {
      throw new EsiaException(e);
    }
  }

  @Override
  public PersonModel getPersonData(String prnsId, String accessToken) {
    try {
      String url = esiaConfig.getEsiaBaseUri() + "rs/prns/"  + prnsId;
      HttpRequest getReq = HttpRequest.newBuilder(URI.create(url))
          .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
          .header("Authorization", "Bearer ".concat(accessToken))
          .GET()
          .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout()))
          .build();
      HttpResponse<String> getResp = HttpClient.newBuilder()
          .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout()))
          .build()
          .send(getReq, HttpResponse.BodyHandlers.ofString());
      errorHandler(getResp);
      return objectMapper.readValue(getResp.body(), PersonModel.class);
    }
    catch (Exception e) {
      throw new EsiaException(e);
    }
  }

  @Override
  public EsiaAccessToken readToken(String accessToken) {
    try {
      byte[] decodedBytes = Base64.getDecoder()
          .decode(
              accessToken.substring(accessToken.indexOf('.') + 1, accessToken.lastIndexOf('.'))
                  .replace('-', '+')
                  .replace('_', '/'));
      String decodedString = new String(decodedBytes);
      EsiaAccessToken esiaAccessToken = objectMapper.readValue(decodedString,
          EsiaAccessToken.class
      );
      return esiaAccessToken;
    }
    catch (Exception e) {
      throw new EsiaException(e);
    }
  }

  @Override
  public EsiaHeader readHeader(String accessToken) {
    try {
      byte[] decodedBytes = Base64.getDecoder()
          .decode(
              accessToken.substring(0, accessToken.indexOf('.'))
                  .replace('-', '+')
                  .replace('_', '/'));
      String decodedString = new String(decodedBytes);
      EsiaHeader esiaHeader = objectMapper.readValue(decodedString,
          EsiaHeader.class
      );
      return esiaHeader;
    }
    catch (Exception e) {
      throw new EsiaException(e);
    }
  }

  private OrganizationModel getOrgModel(String orgOid, String accessToken) {
    try {
      String url = esiaConfig.getEsiaBaseUri() + "rs/orgs/" + orgOid;
      HttpRequest getReq = HttpRequest.newBuilder(URI.create(url + "?embed=(contacts.elements,addresses.elements)"))
          .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
          .header("Authorization", "Bearer ".concat(accessToken))
          .GET()
          .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout()))
          .build();
      HttpResponse<String> getResp = HttpClient.newBuilder()
          .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout()))
          .build()
          .send(getReq, HttpResponse.BodyHandlers.ofString());
      errorHandler(getResp);
      OrganizationModel organizationModel = objectMapper.readValue(getResp.body(),
          OrganizationModel.class
      );
      HttpRequest getReqBrhs = HttpRequest.newBuilder(
              URI.create(url + "/brhs?embed=(elements,addresses.elements,contacts.elements)"))
          .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
          .header("Authorization", "Bearer ".concat(accessToken))
          .GET()
          .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout()))
          .build();
      HttpResponse<String> getRespBrhs = HttpClient.newBuilder()
          .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout()))
          .build()
          .send(getReqBrhs, HttpResponse.BodyHandlers.ofString());
      errorHandler(getRespBrhs);
      BrhsModel[] brhsModels = objectMapper.readValue(
          objectMapper.readTree(getRespBrhs.body()).get("elements").toString(), BrhsModel[].class);
      organizationModel.setBrhs(brhsModels);
      return organizationModel;
    }
    catch (Exception e) {
      throw new EsiaException(e);
    }
  }

  private void errorHandler(HttpResponse httpResponse) {
    if (httpResponse.statusCode() != 200) {
      throw new EsiaException(httpResponse.statusCode() + " " + httpResponse.body());
    }
  }

  @Override
  public boolean checkRole(String accessToken) {
    try {
      EsiaAccessToken esiaAccessToken = readToken(accessToken);
      String scope = esiaAccessToken.getScope();
      String orgOid = scope.substring(scope.indexOf('=') + 1, scope.indexOf(' '));
      String prnsId = esiaAccessToken.getSbjId();
      String url = esiaConfig.getEsiaBaseUri() + "rs/orgs/"  + orgOid + "/emps/" + prnsId + "/grps?embed=(elements)";
      HttpRequest getReq = HttpRequest.newBuilder(URI.create(url))
          .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
          .header("Authorization", "Bearer ".concat(accessToken))
          .GET()
          .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout()))
          .build();
      HttpResponse<String> getResp = HttpClient.newBuilder()
          .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout()))
          .build()
          .send(getReq, HttpResponse.BodyHandlers.ofString());
      errorHandler(getResp);
      if (getResp.body().contains(esiaConfig.getEsiaUploadDataRole())) {
        return true;
      }
      else {
        return false;
      }
    }
    catch (Exception e) {
      throw new EsiaException(e);
    }
  }

  @Override
  public String getAllUserRoles(String accessToken) {
    try {
      EsiaAccessToken esiaAccessToken = readToken(accessToken);
      String scope = esiaAccessToken.getScope();
      String orgOid = scope.substring(scope.indexOf('=') + 1, scope.indexOf(' '));
      String prnsId = esiaAccessToken.getSbjId();
      String url = esiaConfig.getEsiaBaseUri() + "rs/orgs/"  + orgOid + "/emps/" + prnsId + "/grps?embed=(elements)";
      HttpRequest getReq = HttpRequest.newBuilder(URI.create(url))
          .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
          .header("Authorization", "Bearer ".concat(accessToken))
          .GET()
          .timeout(Duration.ofSeconds(esiaConfig.getRequestTimeout()))
          .build();
      HttpResponse<String> getResp = HttpClient.newBuilder()
          .connectTimeout(Duration.ofSeconds(esiaConfig.getConnectionTimeout()))
          .build()
          .send(getReq, HttpResponse.BodyHandlers.ofString());
      errorHandler(getResp);
      JsonNode rootNode = objectMapper.readTree(getResp.body());
      JsonNode elementsNode = rootNode.path("elements");
      StringBuilder names = new StringBuilder();
      for (JsonNode element : elementsNode) {
        if (element.path("itSystem").asText().equals(esiaConfig.getClientId())) {
          String name = element.path("name").asText();
          names.append(name).append("\n");
        }
      }
      return names.toString();
    }
    catch (Exception e) {
      throw new EsiaException(e);
    }
  }
}
