package ru.micord.ervu.account_applications.service;

import java.net.URI;
import java.util.Map;
import java.util.List;
import java.util.Set;
import java.util.HashMap;
import java.util.ArrayList;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import component.editablegrid.columns.EditableGridColumn;
import model.grid.GridRow;
import model.grid.GridRows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import ru.micord.ervu.account_applications.component.exception.UserDataLoadException;
import ru.micord.ervu.account_applications.component.model.Account;
import ru.micord.ervu.account_applications.component.model.Role;
import ru.micord.ervu.account_applications.component.model.dto.SearchRequest;
import ru.micord.ervu.account_applications.component.model.dto.SearchResponse;
import ru.micord.ervu.account_applications.security.context.SecurityContext;
import ru.micord.ervu.account_applications.service.constant.PathConstant;

import ru.cg.webbpm.modules.standard_annotations.editor.ObjectRef;

import static ru.micord.ervu.account_applications.enums.BlockedRegion.*;

/**
 * @author Adel Kalimullin
 */
@Service
public class AccountFetchService implements EntityFetchService<Account> {
  private static final String FIELD_SEX = "sex";
  private static final String FIELD_IP_ADDRESSES = "ipAddresses";
  private static final String FIELD_SNILS = "snils";
  private static final String FIELD_ROLES = "roles";
  private static final String FIELD_ACCOUNT_BLOCKED = "blocked";
  private static final String FIELD_PERSON_BLOCKED = "person.blocked";
  private static final String FIELD_BLOCKED_REGION = "blockedRegion";
  private final RestTemplate restTemplate;
  private final ObjectMapper mapper;
  private final SecurityContext securityContext;
  @Value("${idm.url}")
  private String idmUrl;
  @ObjectRef
  public EditableGridColumn editableGridColumnRef;


  public AccountFetchService(RestTemplate restTemplate, ObjectMapper mapper,
      SecurityContext securityContext) {
    this.restTemplate = restTemplate;
    this.mapper = mapper;
    this.securityContext = securityContext;
  }

  @Override
  public Map<String, Object> fetchDataById(Object id) {
    Account account = fetchAccountById(id);
    return objectToFlatMap(account);
  }

  @Override
  public SearchResponse<Account> fetchByFilter(SearchRequest request) {
    try {
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_JSON);
      headers.setBearerAuth(securityContext.getToken());
      HttpEntity<SearchRequest> httpEntity = new HttpEntity<>(request, headers);

      URI uri = UriComponentsBuilder.fromHttpUrl(idmUrl)
          .pathSegment(PathConstant.ACCOUNTS_PATH, "search", "v1")
          .build()
          .toUri();

      ResponseEntity<SearchResponse<Account>> response = restTemplate.exchange(
          uri,
          HttpMethod.POST,
          httpEntity,
          new ParameterizedTypeReference<SearchResponse<Account>>() {
          }
      );

      return response.getBody();
    }
    catch (Exception e) {
      throw new UserDataLoadException("Ошибка при поиске аккаунтов", e);
    }
  }

  public List<String> fetchRemovedRoleIdsByAccountId(String accountId, Set<String> newRoleIds) {
    try {
      Account account = fetchAccountById(accountId);
      List<Role> roles = account.getRoles();

      if (roles == null) {
        return List.of();
      }

      return roles.stream()
          .map(Role::getId)
          .filter(roleId -> !newRoleIds.contains(roleId))
          .toList();
    }
    catch (Exception e) {
      throw new UserDataLoadException("Ошибка при получении ролей пользователя", e);
    }
  }

  private Account fetchAccountById(Object id) {
    URI uri = UriComponentsBuilder.fromHttpUrl(idmUrl)
        .pathSegment(PathConstant.ACCOUNTS_PATH, id.toString(), "v1")
        .build()
        .toUri();

    try {
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_JSON);
      headers.setBearerAuth(securityContext.getToken());
      HttpEntity<Void> request = new HttpEntity<>(headers);

      ResponseEntity<Account> response = restTemplate.exchange(
          uri,
          HttpMethod.GET,
          request,
          Account.class
      );

      return response.getBody();
    }
    catch (Exception e) {
      throw new UserDataLoadException("Ошибка при выполнении GET-запроса", e);
    }
  }

  private Map<String, Object> objectToFlatMap(Object obj) {
    Map<String, Object> result = objectToFlatMapInternal(obj, "");
    putBlockedValue(result);
    return result;
  }

  private Map<String, Object> objectToFlatMapInternal(Object obj, String prefix) {

    Map<String, Object> result = new HashMap<>();
    if (obj == null) {
      return result;
    }

    Map<String, Object> map = mapper.convertValue(obj, new TypeReference<>() {
    });

    for (Map.Entry<String, Object> entry : map.entrySet()) {
      String key = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
      Object value = entry.getValue();

      if (value == null || (value instanceof String str && str.isBlank())) {
        continue;
      }

      if (FIELD_SEX.equalsIgnoreCase(entry.getKey()) && value instanceof String sex) {
        result.put(key, sex.toLowerCase());
        continue;
      }
      if (FIELD_SNILS.equalsIgnoreCase(entry.getKey()) && value instanceof String snils) {
        result.put(key, snils.replaceAll("\\D", ""));
        continue;
      }

      if (value instanceof List<?> list && !list.isEmpty()) {
        if (FIELD_IP_ADDRESSES.equalsIgnoreCase(entry.getKey())) {
          result.put(key, convertIpAddressesToGridRows(list));
        }
        else if (FIELD_ROLES.equalsIgnoreCase(entry.getKey())) {
          result.put(key, convertRolesToGridRows(list));
        }
        continue;
      }

      if (isSimple(value.getClass())) {
        result.put(key, value);
      }
      else {
        result.putAll(objectToFlatMapInternal(value, key));
      }
    }
    return result;
  }

  private GridRows convertIpAddressesToGridRows(List<?> ipList) {
    List<GridRow> rows = new ArrayList<>();
    int i = 0;
    for (Object ip : ipList) {
      GridRow row = new GridRow();
      row.put("row_uid", ip);
      row.put(
          editableGridColumnRef != null ? editableGridColumnRef.getObjectId() : FIELD_IP_ADDRESSES,
          ip
      );
      rows.add(row);
    }
    return new GridRows(rows);
  }

  private GridRows convertRolesToGridRows(List<?> list) {
    List<GridRow> rows = new ArrayList<>();
    for (Object item : list) {
      if (item instanceof Map<?, ?> map) {
        GridRow row = new GridRow();
        row.put("row_uid", map.get("id"));
        row.put("name", map.get("name"));
        row.put("displayName", map.get("displayName"));
        row.put("description", map.get("description"));
        row.put("active", map.get("active"));
        rows.add(row);
      }
    }
    return new GridRows(rows);
  }

  private boolean isSimple(Class<?> type) {
    return ClassUtils.isPrimitiveOrWrapper(type) || type == String.class;
  }

  private void putBlockedValue(Map<String, Object> result) {
    boolean accountBlocked = Boolean.parseBoolean(String.valueOf(result.get(FIELD_ACCOUNT_BLOCKED)));
    boolean personBlocked = Boolean.parseBoolean(String.valueOf(result.get(FIELD_PERSON_BLOCKED)));
    String value = accountBlocked && personBlocked
                   ? BOTH.toString() : accountBlocked
                              ? ACCOUNT.toString() : personBlocked
                                            ? PERSON.toString() : null;
    result.put(FIELD_BLOCKED_REGION, value);
  }
}
