package com.arena.combat.service;

import com.arena.combat.config.OpenAiProperties;
import com.arena.combat.dto.response.RespuestaJefeIA;
import com.arena.combat.entity.PartidaCombate;
import com.arena.combat.entity.TurnoHistorial;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.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.*;

@Service
@RequiredArgsConstructor
@Slf4j
public class OpenAiService {

    private final OpenAiProperties openAiProperties;
    private final ObjectMapper objectMapper;

    private final HttpClient httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();

    /**
     * Solicita la decisión táctica a OpenAI (gpt-4o-mini) con salida JSON estructurada y determinista.
     * Si no hay API Key o falla la red, utiliza un motor heurístico adaptativo de respaldo para garantizar
     * alta disponibilidad y continuidad del combate.
     */
    public RespuestaJefeIA obtenerDecisionJefe(PartidaCombate partida,
                                               String ultimaAccionJugador,
                                               int danoInfligidoPorJugador,
                                               List<TurnoHistorial> ultimosTurnos) {
        if (!openAiProperties.isConfigured()) {
            log.info("OPENAI_API_KEY no configurada o en modo demo. Ejecutando motor táctico adaptativo interno.");
            return generarDecisionHeuristica(partida, ultimaAccionJugador, danoInfligidoPorJugador);
        }

        try {
            String systemPrompt = construirSystemPrompt();
            String userPrompt = construirUserPrompt(partida, ultimaAccionJugador, danoInfligidoPorJugador, ultimosTurnos);

            Map<String, Object> payload = new LinkedHashMap<>();
            payload.put("model", openAiProperties.getModel());
            payload.put("temperature", 0.7);

            // Forzar salida JSON estricta (Structured Outputs con json_object / json_schema)
            Map<String, String> responseFormat = new HashMap<>();
            responseFormat.put("type", "json_object");
            payload.put("response_format", responseFormat);

            List<Map<String, String>> messages = new ArrayList<>();
            messages.add(Map.of("role", "system", "content", systemPrompt));
            messages.add(Map.of("role", "user", "content", userPrompt));
            payload.put("messages", messages);

            String requestBody = objectMapper.writeValueAsString(payload);

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(openAiProperties.getApiUrl()))
                    .timeout(Duration.ofSeconds(openAiProperties.getTimeoutSeconds()))
                    .header("Content-Type", "application/json")
                    .header("Authorization", "Bearer " + openAiProperties.getApiKey())
                    .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                    .build();

            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() >= 200 && response.statusCode() < 300) {
                JsonNode rootNode = objectMapper.readTree(response.body());
                JsonNode contentNode = rootNode.path("choices").path(0).path("message").path("content");

                if (!contentNode.isMissingNode()) {
                    String jsonContenido = contentNode.asText();
                    RespuestaJefeIA decision = objectMapper.readValue(jsonContenido, RespuestaJefeIA.class);
                    validarYNormalizarDecision(decision);
                    log.info("Decisión obtenida exitosamente desde gpt-4o-mini: {}", decision);
                    return decision;
                }
            } else {
                log.warn("Llamada a OpenAI retornó código {}: {}. Activando contingencia heurística.",
                        response.statusCode(), response.body());
            }
        } catch (Exception e) {
            log.error("Excepción al consumir OpenAI API: {}. Activando contingencia táctica.", e.getMessage());
        }

        return generarDecisionHeuristica(partida, ultimaAccionJugador, danoInfligidoPorJugador);
    }

    private String construirSystemPrompt() {
        return """
            Eres el cerebro táctico de un Jefe de Incursión en un videojuego RPG de combate 2D por turnos.
            Tu objetivo es analizar la situación del combate, la clase del jugador, su salud y su último movimiento para elegir la mejor táctica.
            
            DEBES responder OBLIGATORIAMENTE con un objeto JSON válido que respete estrictamente este esquema:
            {
              "accion_jefe": "STRING (Debe ser exactamente uno de: ATAQUE_PESADO | MAGIA_OSCURA | CURACION | CONTRAATAQUE)",
              "nombre_tecnica": "STRING (Nombre creativo y amenazante de la técnica)",
              "multiplicador_dano": "NUMBER (Valor decimal entre 0.8 y 1.5)",
              "dialogo_jefe": "STRING (Frase intimidante, sarcástica o reactiva dirigida al héroe según su última acción)",
              "razonamiento_tactico": "STRING (Explicación breve de 1 frase del por qué elegiste este movimiento)"
            }
            
            Reglas Tácticas:
            - Si tu HP es menor al 30%, prioriza 'CURACION' o un 'ATAQUE_PESADO' desesperado.
            - Si el jugador se defendió el turno pasado, usa 'MAGIA_OSCURA' para castigar su pasividad o 'CURACION'.
            - Si el jugador te causó mucho daño físico o elemental, usa 'CONTRAATAQUE' o 'ATAQUE_PESADO'.
            - No incluyas markdown (nada de ```json), solo el JSON puro deserializable.
            """;
    }

    private String construirUserPrompt(PartidaCombate partida,
                                       String ultimaAccionJugador,
                                       int danoInfligidoPorJugador,
                                       List<TurnoHistorial> ultimosTurnos) {
        StringBuilder sb = new StringBuilder();
        sb.append("=== ESTADO ACTUAL DEL COMBATE ===\n");
        sb.append("Turno actual: ").append(partida.getTurnoActual()).append("\n");
        sb.append("Jefe: ").append(partida.getNombreJefe()).append(" (").append(partida.getTipoJefe()).append(")\n");
        sb.append("HP Jefe: ").append(partida.getHpActualJefe()).append("/").append(partida.getHpMaxJefe())
                .append(" (").append(Math.round((double) partida.getHpActualJefe() / partida.getHpMaxJefe() * 100)).append("%)\n");
        sb.append("Héroe Clase: ").append(partida.getUsuario().getClasePersonaje()).append("\n");
        sb.append("HP Héroe: ").append(partida.getHpActualHeroe()).append("/").append(partida.getHpMaxHeroe())
                .append(" (").append(Math.round((double) partida.getHpActualHeroe() / partida.getHpMaxHeroe() * 100)).append("%)\n");
        sb.append("MP Héroe: ").append(partida.getMpActualHeroe()).append("/").append(partida.getMpMaxHeroe()).append("\n");
        sb.append("Última Acción del Jugador: ").append(ultimaAccionJugador).append("\n");
        sb.append("Daño recibido en este turno por el Jefe: ").append(danoInfligidoPorJugador).append("\n");
        sb.append("¿El jugador está en postura defensiva?: ").append(partida.getEnDefensa()).append("\n");

        if (ultimosTurnos != null && !ultimosTurnos.isEmpty()) {
            sb.append("\n=== HISTORIAL RECIENTE ===\n");
            for (TurnoHistorial th : ultimosTurnos) {
                sb.append("- Turno ").append(th.getNumeroTurno()).append(": Jugador hizo ").append(th.getAccionJugador()).append("\n");
            }
        }

        sb.append("\nGenera tu decisión táctica en el JSON solicitado.");
        return sb.toString();
    }

    private void validarYNormalizarDecision(RespuestaJefeIA decision) {
        if (decision.getAccionJefe() == null || decision.getAccionJefe().isBlank()) {
            decision.setAccionJefe("ATAQUE_PESADO");
        }
        String accion = decision.getAccionJefe().toUpperCase().trim();
        if (!accion.equals("ATAQUE_PESADO") && !accion.equals("MAGIA_OSCURA") &&
            !accion.equals("CURACION") && !accion.equals("CONTRAATAQUE")) {
            decision.setAccionJefe("ATAQUE_PESADO");
        } else {
            decision.setAccionJefe(accion);
        }

        if (decision.getMultiplicadorDano() == null || decision.getMultiplicadorDano() < 0.8 || decision.getMultiplicadorDano() > 1.5) {
            decision.setMultiplicadorDano(1.0);
        }

        if (decision.getNombreTecnica() == null || decision.getNombreTecnica().isBlank()) {
            decision.setNombreTecnica("Corte Devastador del Abismo");
        }

        if (decision.getDialogoJefe() == null || decision.getDialogoJefe().isBlank()) {
            decision.setDialogoJefe("¡Tu resistencia solo retrasa lo inevitable, mortal!");
        }

        if (decision.getRazonamientoTactico() == null || decision.getRazonamientoTactico().isBlank()) {
            decision.setRazonamientoTactico("Aprovechamiento de la apertura del rival.");
        }
    }

    /**
     * Motor táctico heurístico adaptativo para contingencia (cuando no hay API key o no hay red).
     */
    private RespuestaJefeIA generarDecisionHeuristica(PartidaCombate partida,
                                                      String ultimaAccionJugador,
                                                      int danoJugador) {
        double porcentajeHpjefe = (double) partida.getHpActualJefe() / partida.getHpMaxJefe();
        double porcentajeHpHeroe = (double) partida.getHpActualHeroe() / partida.getHpMaxHeroe();

        // 1. Si el jefe está en estado crítico (< 25% HP), busca curarse o un ataque desesperado
        if (porcentajeHpjefe < 0.25) {
            if (Math.random() < 0.6) {
                return RespuestaJefeIA.builder()
                        .accionJefe("CURACION")
                        .nombreTecnica("Pacto de Sangre Abisal")
                        .multiplicadorDano(0.8)
                        .dialogoJefe("¿Crees que puedes derribarme tan fácilmente? ¡Las sombras restauran mi vigor!")
                        .razonamientoTactico("Salud crítica (<25%). Regeneración necesaria para prolongar el combate.")
                        .build();
            } else {
                return RespuestaJefeIA.builder()
                        .accionJefe("ATAQUE_PESADO")
                        .nombreTecnica("Colapso del Vacío")
                        .multiplicadorDano(1.45)
                        .dialogoJefe("¡Si he de caer, te arrastraré conmigo al fondo de la nada!")
                        .razonamientoTactico("Ataque desesperado de máxima potencia para liquidar al rival antes de perecer.")
                        .build();
            }
        }

        // 2. Si el jugador se defendió
        if (Boolean.TRUE.equals(partida.getEnDefensa()) || "DEFENDER".equalsIgnoreCase(ultimaAccionJugador)) {
            return RespuestaJefeIA.builder()
                    .accionJefe("MAGIA_OSCURA")
                    .nombreTecnica("Llama Fantasmal Corrosiva")
                    .multiplicadorDano(1.2)
                    .dialogoJefe("¿Te escondes tras tu escudo? ¡Las llamas etéreas penetran cualquier metal!")
                    .razonamientoTactico("El objetivo asumió postura defensiva física; se perfora mediante magia elemental.")
                    .build();
        }

        // 3. Si el jugador le causó daño alto o usó magia
        if ("MAGIA_ELEMENTAL".equalsIgnoreCase(ultimaAccionJugador) || danoJugador > 25) {
            return RespuestaJefeIA.builder()
                    .accionJefe("CONTRAATAQUE")
                    .nombreTecnica("Reflejo Espectral Sombrío")
                    .multiplicadorDano(1.3)
                    .dialogoJefe("¡Tu insolencia mágica será castigada con tu propia energía multiplicada!")
                    .razonamientoTactico("Respuesta inmediata ante la descarga de daño del adversario con contraofensiva.")
                    .build();
        }

        // 4. Si el héroe tiene poca vida (< 35%), remate
        if (porcentajeHpHeroe < 0.35) {
            return RespuestaJefeIA.builder()
                    .accionJefe("ATAQUE_PESADO")
                    .nombreTecnica("Guadaña del Fin del Tiempo")
                    .multiplicadorDano(1.35)
                    .dialogoJefe("Huelo tu agonía... ¡Es momento de poner fin a esta patética exhibición!")
                    .razonamientoTactico("El héroe está en rango de ejecución. Golpe contundente para sentenciar.")
                    .build();
        }

        // 5. Rutina estándar balanceada
        List<RespuestaJefeIA> opciones = List.of(
                RespuestaJefeIA.builder()
                        .accionJefe("ATAQUE_PESADO")
                        .nombreTecnica("Tajo Hendidor Sombrío")
                        .multiplicadorDano(1.15)
                        .dialogoJefe("¡Siente el peso de la oscuridad aplastando tus huesos!")
                        .razonamientoTactico("Presión física directa para desgastar la barra de vitalidad del oponente.")
                        .build(),
                RespuestaJefeIA.builder()
                        .accionJefe("MAGIA_OSCURA")
                        .nombreTecnica("Tormenta de Almas Enfadadas")
                        .multiplicadorDano(1.25)
                        .dialogoJefe("Los lamentos de los caídos desgarrarán tu espíritu mortal.")
                        .razonamientoTactico("Ataque de área y presión psicológica para neutralizar la agilidad del rival.")
                        .build(),
                RespuestaJefeIA.builder()
                        .accionJefe("CONTRAATAQUE")
                        .nombreTecnica("Estocada del Vórtice")
                        .multiplicadorDano(1.1)
                        .dialogoJefe("Te mueves demasiado lento para alguien que pretende llamarse héroe.")
                        .razonamientoTactico("Maniobra de interceptación y castigo de tempo.")
                        .build()
        );

        return opciones.get(new Random().nextInt(opciones.size()));
    }
}
