package mx.gob.jumapacelaya.api;

import com.google.gson.*;
import mx.gob.jumapacelaya.models.RedmineUser;
import mx.gob.jumapacelaya.models.Ticket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class RedmineClient {
    private static final Logger log = LoggerFactory.getLogger(RedmineClient.class);

    //private static final int PAGE_SIZE = 25;
    static String REDMINE_URL;
    static String API_KEY;

    public RedmineClient(@Value("${redmine.url}") String redmineUrl, @Value("${redmine.api_key}") String apiKey) {
        REDMINE_URL = redmineUrl;
        API_KEY = apiKey;

    }

    //AQUI OBTENGO LOS TICKETS DESDE REDMINE
    public List<Ticket> getTickets(RedmineUser user, boolean includeClosed,
                                   int offset, int limit,
                                   LocalDate fechaDesde,
                                   LocalDate fechaHasta) {

        List<Ticket> tickets = new ArrayList<>();
        HttpClient client = HttpClient.newHttpClient();

        // Filtrar tickets abiertos o cerrados
        String statusFilter = includeClosed ? "&status_id=open" : "&status_id=closed";

        StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey()
                + statusFilter + "&offset=" + offset + "&limit=" + limit);
        url.append("&sort=created_on:desc");


        // Filtro por fechas
        DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
        if (fechaDesde != null || fechaHasta != null) {
            url.append("&created_on=");

            if (fechaDesde != null && fechaHasta != null) {
                // En lugar de >< usamos %3E%3C y en lugar de | usamos %7C
                url.append("%3E%3C")
                        .append(fechaDesde.toString())
                        .append("%7C")
                        .append(fechaHasta.toString());
            } else if (fechaDesde != null) {
                url.append("%3E%3D").append(fechaDesde.toString());
            } else {
                url.append("%3C%3D").append(fechaHasta.toString());
            }
        }

        //log.info("URL: " + url.toString());

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url.toString()))
                .header("Content-Type", "application/json")
                .build();

        try {
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() == 200) {
                tickets.addAll(parseTickets(response.body()));
            } else {
                log.error("Error al obtener tickets. Codigo de estado: {}", response.statusCode());
            }
        } catch (Exception e) {
            log.error("Ocurrió una excepción al procesar la respuesta del servidor: ", e);
        }

        return tickets;
    }

    public int getTotalTickets(RedmineUser user,
                               boolean includeClosed,
                               LocalDate fechaDesde,
                               LocalDate fechaHasta) {

        HttpClient client = HttpClient.newHttpClient();

        // Filtrar tickets abiertos o cerrados
        String statusFilter = includeClosed ? "&status_id=open" : "&status_id=closed";

        StringBuilder url = new StringBuilder(REDMINE_URL + "/issues.json?key=" + user.getKey()
                + statusFilter + "&limit=1");

        // Filtro por fechas
        DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
        if (fechaDesde != null || fechaHasta != null) {
            url.append("&created_on=");

            if (fechaDesde != null && fechaHasta != null) {
                // En lugar de >< usamos %3E%3C y en lugar de | usamos %7C
                url.append("%3E%3C")
                        .append(fechaDesde.toString())
                        .append("%7C")
                        .append(fechaHasta.toString());
            } else if (fechaDesde != null) {
                url.append("%3E%3D").append(fechaDesde.toString());
            } else {
                url.append("%3C%3D").append(fechaHasta.toString());
            }
        }

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url.toString()))
                .header("Content-Type", "application/json")
                .build();

        try {
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() == 200) {
                JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject();
                return jsonObject.get("total_count").getAsInt();
            } else {
                log.error("Error al obtener los tickets. Codigo de estado: {}", response.statusCode());
            }
        } catch (Exception e) {
            log.error("Ocurrió un error al procesar l respuesta del servidor", e);
        }
        return 0;
    }


    // Aquí se parsean todos los tickets que existen y se les da un formato con los campos a mostrarse
    private List<Ticket> parseTickets(String json) {
        List<Ticket> tickets = new ArrayList<>();
        try {
            JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
            JsonArray issues = jsonObject.getAsJsonArray("issues");
            if (issues != null) {
                for (JsonElement issueElement : issues) {
                    JsonObject issue = issueElement.getAsJsonObject();

                    // ID, subject, descripción
                    int id = issue.has("id") && !issue.get("id").isJsonNull() ? issue.get("id").getAsInt() : 0;
                    String subject = issue.has("subject") && !issue.get("subject").isJsonNull() ? issue.get("subject").getAsString() : "";
                    String description = issue.has("description") && !issue.get("description").isJsonNull() ? issue.get("description").getAsString() : "";
                    Double estimatedHrs = issue.has("estimated_hours") && !issue.get("estimated_hours").isJsonNull() ? issue.get("estimated_hours").getAsDouble() : 0.0;

                    // Status
                    String status = "Unknown";
                    if (issue.has("status") && !issue.get("status").isJsonNull()) {
                        JsonObject statusObject = issue.getAsJsonObject("status");
                        if (statusObject.has("name") && !statusObject.get("name").isJsonNull()) {
                            status = statusObject.get("name").getAsString();
                        }
                    }

                    // Parse fechas correctamente
                    LocalDateTime dateCreate = parseDateTime(issue, "created_on");
                    LocalDateTime dateUpdate = parseDateTime(issue, "updated_on");
                    LocalDateTime dateClose  = parseDateTime(issue, "closed_on");

                    // Autor
                    Ticket.User autor = null;
                    if (issue.has("author") && !issue.get("author").isJsonNull()) {
                        JsonObject authorObj = issue.getAsJsonObject("author");
                        if (authorObj.has("name") && !authorObj.get("name").isJsonNull()) {
                            autor = new Ticket.User(authorObj.get("name").getAsString());
                        }
                    }

                    // Tracker ID
                    Integer trackerId = null;
                    if (issue.has("tracker") && !issue.get("tracker").isJsonNull()) {
                        JsonObject trackerObject = issue.getAsJsonObject("tracker");
                        if (trackerObject.has("id") && !trackerObject.get("id").isJsonNull()) {
                            trackerId = trackerObject.get("id").getAsInt();
                        }
                    }

                    // Crear ticket
                    Ticket ticketObj = new Ticket(
                            id,
                            subject,
                            description,
                            status,
                            dateCreate != null ? dateCreate.toString() : "",
                            dateClose != null ? dateClose.toString() : "",
                            dateUpdate != null ? dateUpdate.toString() : "",
                            autor,
                            trackerId,
                            "Tipo Desconocido",
                            estimatedHrs

                    );

                    // Log para verificar JSON de ticket
                    //System.out.println(GSON.toJson(ticketObj));

                    tickets.add(ticketObj);
                }
            } else {
                log.info("La respuesta JSON no contiene la clave 'issues'. Respuesta completa: {}", json);
            }
        } catch (Exception e) {
            log.error("Ocurrió un error al parsear los tickets: ", e);
        }
        return tickets;
    }


    public RedmineUser getMyAccount(String username) {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(REDMINE_URL + "/my/account.json"))
                .header("Content-Type", "application/json")
                .header("X-Redmine-Switch-User", username)
                .header("X-Redmine-API-Key", API_KEY)
                .GET()
                .build();
        try {
            HttpClient client = HttpClient.newHttpClient();
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

            JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject();
            JsonObject userJson = jsonObject.get("user").getAsJsonObject();

            RedmineUser user = new RedmineUser();
            user.setId(userJson.get("id").getAsInt());
            user.setLogin(userJson.get("login").getAsString());
            user.setFirstname(userJson.get("firstname").getAsString());
            user.setLastname(userJson.get("lastname").getAsString());
            user.setMail(userJson.get("mail").getAsString());
            user.setKey(userJson.get("api_key").getAsString());
            return user;

            //return response.body();
        } catch (IOException | InterruptedException e) {
            log.error("Error al obtener la cuenta del usuario: {}", e);
            return null;
        }
    }


    public static RedmineUser createRedmineUser(String username, String firstname, String lastname, String mail) {
        HttpClient client = HttpClient.newHttpClient();
        Map<String, Object> user = new HashMap<>();
        user.put("login", username);
        user.put("firstname", firstname);
        user.put("lastname", lastname);
        user.put("mail", mail);
        user.put("auth_source_id", "1");

        Map<String, Object> payload = new HashMap<>();
        payload.put("user", user);
        String jsonPayload = new Gson().toJson(payload);
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(REDMINE_URL + "/users.json"))
                .header("Content-Type", "application/json")
                .header("X-Redmine-API-Key", API_KEY)
                .POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
                .build();

        try {
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

            JsonObject jsonObject = JsonParser.parseString(response.body()).getAsJsonObject();
            JsonObject userJson = jsonObject.get("user").getAsJsonObject();

            RedmineUser newUser = new RedmineUser();
            newUser.setId(userJson.get("id").getAsInt());
            newUser.setLogin(userJson.get("login").getAsString());
            newUser.setFirstname(userJson.get("firstname").getAsString());
            newUser.setLastname(userJson.get("lastname").getAsString());
            newUser.setMail(userJson.get("mail").getAsString());

            return newUser;

        } catch (Exception e) {
            log.error("Error al crear el usuario: {}", e);
            return null;
        }
    }

    //AQUI OBTENGO A LOS USUARIOS
    //Se devuelven en formato Json
    public RedmineUser getUserByUsername(String username) {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(REDMINE_URL + "/users.json?name=" + username))
                .header("Content-Type", "application/json")
                .header("X-Redmine-API-Key", API_KEY)
                .build();

        try {
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() == 200) {
                String responseBody = response.body();
                log.info("Datos del usuario " + responseBody);
                return parseUser(responseBody);
            } else {
                log.error("Error en la respuesta {}", response.statusCode());
            }
        } catch (Exception e) {
            log.error("Error al obtener el usuario por nombre de usuario: {}", e);
        }
        return null;
    }

    //Aqui se parsean a los usuarios y se les da un formato con los campos a mostrarse
    private RedmineUser parseUser(String json) {
        JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
        JsonObject userJson = jsonObject.getAsJsonArray("users").get(0).getAsJsonObject();

        RedmineUser user = new RedmineUser();
        user.setId(userJson.get("id").getAsInt());
        user.setLogin(userJson.get("login").getAsString());
        user.setFirstname(userJson.get("firstname").getAsString());
        user.setLastname(userJson.get("lastname").getAsString());
        user.setMail(userJson.get("mail").getAsString());
        //user.setKey(userJson.get("key").getAsString());
        return user;
    }


    /*Este metodo sirve para actualizar el estatus de los tickets
    en este caso se actualiza al estatus TERMINADO */
    public void closeTicket(int ticketId, RedmineUser user) throws IOException, InterruptedException {
        int closedStatusId = 5; //este es el ID del estado TERMINADO, se debe poner el id a según corresponda

        Map<String, Object> payload = new HashMap<>();
        Map<String, Object> issue = new HashMap<>();
        issue.put("status_id", closedStatusId);
        payload.put("issue", issue);
        String jsonPayload = GSON.toJson(payload);

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(REDMINE_URL + "/issues/" + ticketId + ".json?key=" + user.getKey()))
                .header("Content-Type", "application/json")
                .PUT(HttpRequest.BodyPublishers.ofString(jsonPayload))
                .build();

        HttpClient client = HttpClient.newHttpClient();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() == 204) {
            String responseBody = response.body();
            log.info("Ticket " + ticketId + " cerrado correctamente.");
        } else {
            log.error("Error al cerrar el ticket: {}", response.statusCode());
            log.error(response.body());
        }
    }

    private static final Gson GSON = new GsonBuilder()
            .registerTypeAdapter(LocalDateTime.class,
                    (JsonSerializer<LocalDateTime>) (src, typeOfSrc, context) ->
                            new JsonPrimitive(src.toString()))
            .registerTypeAdapter(LocalDateTime.class,
                    (JsonDeserializer<LocalDateTime>) (json, typeOf, context) ->
                            LocalDateTime.parse(json.getAsString()))
            .setPrettyPrinting()
            .create();


    // Método auxiliar para parsear echas con hora
    private LocalDateTime parseDateTime(JsonObject issue, String fieldName) {
        if (!issue.has(fieldName) || issue.get(fieldName).isJsonNull()) {
            return null;
        }

        String dateStr = issue.get(fieldName).getAsString();
        try {
            // Si viene con 'Z' al final (UTC), convertir a LocalDateTime
            if (dateStr.endsWith("Z")) {
                return OffsetDateTime.parse(dateStr)
                        .atZoneSameInstant(ZoneId.systemDefault())
                        .toLocalDateTime();
            } else {
                return LocalDateTime.parse(dateStr);
            }
        } catch (DateTimeParseException ex) {
            log.error("Error al parsear fecha '" + fieldName + "': " + dateStr);
            return null;
        }
    }


    public List<Ticket> getAllTickets(RedmineUser user, boolean abiertos,
                                      LocalDate fechaDesde, LocalDate fechaHasta) {
        List<Ticket> allTickets = new ArrayList<>();
        int offset = 0;
        int limit = 100; // Redmine suele permitir hasta 100 por request (puedes probar 1000)

        List<Ticket> batch;
        do {
            batch = getTickets(user, abiertos, offset, limit, fechaDesde, fechaHasta);
            allTickets.addAll(batch);
            offset += limit;
        } while (!batch.isEmpty());

        return allTickets;
    }

}
