package com.arena.combat.service;

import com.arena.combat.dto.request.AccionTurnoRequest;
import com.arena.combat.dto.request.IniciarCombateRequest;
import com.arena.combat.dto.response.*;
import com.arena.combat.entity.PartidaCombate;
import com.arena.combat.entity.TurnoHistorial;
import com.arena.combat.entity.Usuario;
import com.arena.combat.entity.enums.ClasePersonaje;
import com.arena.combat.entity.enums.EstadoPartida;
import com.arena.combat.entity.enums.TipoAccionJugador;
import com.arena.combat.repository.PartidaCombateRepository;
import com.arena.combat.repository.TurnoHistorialRepository;
import com.arena.combat.repository.UsuarioRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Random;

@Service
@RequiredArgsConstructor
@Slf4j
public class CombatService {

    private final PartidaCombateRepository partidaRepository;
    private final TurnoHistorialRepository turnoHistorialRepository;
    private final UsuarioRepository usuarioRepository;
    private final OpenAiService openAiService;
    private final ObjectMapper objectMapper;

    private final Random random = new Random();

    /**
     * Inicializa una nueva sesión de combate 2D y persiste su estado inicial en la BD.
     */
    @Transactional
    public EstadoCombateResponse iniciarCombate(IniciarCombateRequest request) {
        Usuario usuario = usuarioRepository.findById(request.getUsuarioId())
                .orElseThrow(() -> new IllegalArgumentException("Usuario no encontrado con ID: " + request.getUsuarioId()));

        ClasePersonaje clase = usuario.getClasePersonaje();
        int hpMaxHeroe = clase.getHpBase() + (usuario.getNivel() * 10);
        int mpMaxHeroe = clase.getMpBase() + (usuario.getNivel() * 5);

        String nombreJefe = (request.getNombreJefe() != null && !request.getNombreJefe().isBlank())
                ? request.getNombreJefe()
                : "Ignis el Señor de las Sombras";

        String tipoJefe = (request.getTipoJefe() != null && !request.getTipoJefe().isBlank())
                ? request.getTipoJefe()
                : "DRAGON_SOMBRA";

        // Vida balanceada del jefe
        int hpMaxJefe = 200 + (usuario.getNivel() * 20);

        PartidaCombate partida = PartidaCombate.builder()
                .usuario(usuario)
                .nombreJefe(nombreJefe)
                .tipoJefe(tipoJefe)
                .hpMaxHeroe(hpMaxHeroe)
                .hpActualHeroe(hpMaxHeroe)
                .mpMaxHeroe(mpMaxHeroe)
                .mpActualHeroe(mpMaxHeroe)
                .hpMaxJefe(hpMaxJefe)
                .hpActualJefe(hpMaxJefe)
                .turnoActual(1)
                .pocionesHpRestantes(3)
                .pocionesMpRestantes(2)
                .enDefensa(false)
                .estado(EstadoPartida.EN_CURSO)
                .danoTotalInfligido(0)
                .danoTotalRecibido(0)
                .build();

        partida = partidaRepository.save(partida);
        log.info("Nueva partida #{} creada exitosamente para el usuario '{}'", partida.getId(), usuario.getUsername());

        return mapearAEstadoCombate(partida);
    }

    /**
     * Ejecuta el ciclo completo de un turno:
     * 1. Validación de reglas de combate y estado vivo.
     * 2. Cálculo matemático de Java del movimiento del jugador.
     * 3. Consulta estructurada a OpenAI gpt-4o-mini con el contexto del combate.
     * 4. Cálculo matemático de Java del contragolpe del jefe según la táctica de la IA.
     * 5. Persistencia atómica de la partida y del historial inmutable de auditoría.
     */
    @Transactional
    public ResultadoTurnoResponse procesarTurno(AccionTurnoRequest request) {
        PartidaCombate partida = partidaRepository.findById(request.getPartidaId())
                .orElseThrow(() -> new IllegalArgumentException("Partida no encontrada con ID: " + request.getPartidaId()));

        if (partida.estaTerminada()) {
            throw new IllegalStateException("Esta partida ya ha concluido con estado: " + partida.getEstado());
        }

        if (!partida.getUsuario().getId().equals(request.getUsuarioId())) {
            throw new IllegalArgumentException("El usuario especificado no es el propietario de esta partida");
        }

        Usuario usuario = partida.getUsuario();
        int turnoActual = partida.getTurnoActual();

        // 1. CÁLCULO MATEMÁTICO DEL TURNO DEL JUGADOR (JAVA DETERMINISTA)
        CalculoTurnoJugador calculoJugador = ejecutarAccionJugador(partida, request.getTipoAccion(), request.getElementoMagia());

        // Aplicar daño del jugador al jefe
        if (calculoJugador.getDanoCalculado() > 0) {
            int nuevoHpJefe = Math.max(0, partida.getHpActualJefe() - calculoJugador.getDanoCalculado());
            partida.setHpActualJefe(nuevoHpJefe);
            partida.setDanoTotalInfligido(partida.getDanoTotalInfligido() + calculoJugador.getDanoCalculado());
        }

        // 2. VERIFICAR SI EL JEFE FUE DERROTADO TRAS EL ATAQUE DEL JUGADOR
        boolean jefeDerrotado = partida.getHpActualJefe() <= 0;
        RespuestaJefeIA respuestaIa;
        int danoRecibidoPorHeroe = 0;
        int curacionJefe = 0;
        String mensajeJefeResultado;

        if (jefeDerrotado) {
            partida.setEstado(EstadoPartida.VICTORIA);
            respuestaIa = RespuestaJefeIA.builder()
                    .accionJefe("DERROTADO")
                    .nombreTecnica("Lamento Final del Vacío")
                    .multiplicadorDano(0.0)
                    .dialogoJefe("¡Imposible...! Mi poder milenario... desvanecido ante un mortal...")
                    .razonamientoTactico("Puntos de salud reducidos a cero. Colapso total de energía vital.")
                    .build();
            mensajeJefeResultado = "¡El jefe " + partida.getNombreJefe() + " ha caído derrotado!";
        } else {
            // 3. CONSULTA A GPT-4O-MINI PARA DECISIÓN TÁCTICA DEL JEFE
            List<TurnoHistorial> ultimosTurnos = turnoHistorialRepository.findTop3ByPartidaIdOrderByNumeroTurnoDesc(partida.getId());
            respuestaIa = openAiService.obtenerDecisionJefe(partida, request.getTipoAccion().name(), calculoJugador.getDanoCalculado(), ultimosTurnos);

            // 4. CÁLCULO MATEMÁTICO DEL JEFE EJECUTADO EN JAVA
            if ("CURACION".equalsIgnoreCase(respuestaIa.getAccionJefe())) {
                curacionJefe = (int) Math.round(35 * (respuestaIa.getMultiplicadorDano() != null ? respuestaIa.getMultiplicadorDano() : 1.0));
                int nuevoHpjefe = Math.min(partida.getHpMaxJefe(), partida.getHpActualJefe() + curacionJefe);
                partida.setHpActualJefe(nuevoHpjefe);
                mensajeJefeResultado = partida.getNombreJefe() + " ejecutó " + respuestaIa.getNombreTecnica() + " regenerando +" + curacionJefe + " HP.";
            } else {
                int danoBaseJefe = switch (respuestaIa.getAccionJefe()) {
                    case "ATAQUE_PESADO" -> 22 + random.nextInt(6);
                    case "MAGIA_OSCURA" -> 25 + random.nextInt(8);
                    case "CONTRAATAQUE" -> 19 + random.nextInt(5);
                    default -> 18;
                };

                double mult = (respuestaIa.getMultiplicadorDano() != null) ? respuestaIa.getMultiplicadorDano() : 1.0;
                double danoBruto = danoBaseJefe * mult;

                // Mitigación por defensa del jugador
                if (Boolean.TRUE.equals(partida.getEnDefensa())) {
                    danoBruto *= ("MAGIA_OSCURA".equalsIgnoreCase(respuestaIa.getAccionJefe()) ? 0.65 : 0.50);
                }

                // Mitigación por armadura base de la clase
                int defensaClase = usuario.getClasePersonaje().getDefensaBase() / 2;
                danoRecibidoPorHeroe = Math.max(5, (int) Math.round(danoBruto - defensaClase));

                int nuevoHpHeroe = Math.max(0, partida.getHpActualHeroe() - danoRecibidoPorHeroe);
                partida.setHpActualHeroe(nuevoHpHeroe);
                partida.setDanoTotalRecibido(partida.getDanoTotalRecibido() + danoRecibidoPorHeroe);

                mensajeJefeResultado = partida.getNombreJefe() + " usó " + respuestaIa.getNombreTecnica() +
                        " e infligió " + danoRecibidoPorHeroe + " de daño.";

                if (partida.getHpActualHeroe() <= 0) {
                    partida.setEstado(EstadoPartida.DERROTA);
                }
            }
        }

        // Si la postura defensiva estaba activa, se restablece al final del turno
        if (request.getTipoAccion() != TipoAccionJugador.DEFENDER) {
            partida.setEnDefensa(false);
        }

        // Avanzar contador de turno
        partida.setTurnoActual(turnoActual + 1);

        // 5. ACTUALIZAR PROGRESO GLOBAL DEL USUARIO SI TERMINA LA PARTIDA
        int expGanada = 0;
        int nivelPrevio = usuario.getNivel();
        if (partida.getEstado() == EstadoPartida.VICTORIA) {
            expGanada = 120 + (turnoActual * 10);
            usuario.registrarVictoria(expGanada);
            usuarioRepository.save(usuario);
        } else if (partida.getEstado() == EstadoPartida.DERROTA) {
            expGanada = 30 + (turnoActual * 5);
            usuario.registrarDerrota(expGanada);
            usuarioRepository.save(usuario);
        }

        // Guardar cambios en la partida
        partida = partidaRepository.save(partida);

        // 6. PERSISTIR EN HISTORIAL_TURNOS CON FORMATO JSON INMUTABLE
        EstadoCombateResponse estadoActualizado = mapearAEstadoCombate(partida);
        persistirHistorialTurno(partida, usuario, turnoActual, request, calculoJugador, respuestaIa, estadoActualizado);

        return ResultadoTurnoResponse.builder()
                .partidaId(partida.getId())
                .numeroTurno(turnoActual)
                .accionJugador(request.getTipoAccion().name())
                .calculoJugador(calculoJugador)
                .respuestaJefeIa(respuestaIa)
                .danoInfligidoPorJefe(danoRecibidoPorHeroe)
                .curacionJefe(curacionJefe)
                .mensajeJefeResultado(mensajeJefeResultado)
                .estadoPartida(partida.getEstado())
                .estadoCombateActualizado(estadoActualizado)
                .experienciaGanada(expGanada)
                .subioNivel(usuario.getNivel() > nivelPrevio)
                .build();
    }

    private CalculoTurnoJugador ejecutarAccionJugador(PartidaCombate partida,
                                                      TipoAccionJugador tipoAccion,
                                                      String elemento) {
        Usuario usuario = partida.getUsuario();
        ClasePersonaje clase = usuario.getClasePersonaje();

        switch (tipoAccion) {
            case ATAQUE_FISICO -> {
                int base = clase.getAtaqueFisicoBase() + (usuario.getNivel() * 3);
                int variacion = random.nextInt(6);
                boolean critico = random.nextDouble() < 0.20; // 20% probabilidad de crítico
                double factorCritico = critico ? 1.75 : 1.0;
                int dano = (int) Math.round((base + variacion) * factorCritico);

                // Recuperación pasiva de 5 MP en ataque físico
                int mpRecuperado = Math.min(partida.getMpMaxHeroe() - partida.getMpActualHeroe(), 5);
                partida.setMpActualHeroe(partida.getMpActualHeroe() + mpRecuperado);

                return CalculoTurnoJugador.builder()
                        .tipoAccion(tipoAccion)
                        .danoCalculado(dano)
                        .esCritico(critico)
                        .curacionHp(0)
                        .recuperacionMp(mpRecuperado)
                        .costoMp(0)
                        .mensajeResultado(critico ? "¡GOLPE CRÍTICO FÍSICO! Infligiste " + dano + " de daño."
                                                  : "Ataque físico asestado: " + dano + " de daño.")
                        .build();
            }

            case MAGIA_ELEMENTAL -> {
                int costoMp = 15;
                if (partida.getMpActualHeroe() < costoMp) {
                    throw new IllegalArgumentException("Maná insuficiente. Se requieren " + costoMp + " MP.");
                }
                partida.setMpActualHeroe(partida.getMpActualHeroe() - costoMp);

                int base = clase.getPoderMagicoBase() + (usuario.getNivel() * 4);
                int variacion = random.nextInt(10);
                boolean critico = random.nextDouble() < 0.25; // 25% crítico mágico
                double factorCritico = critico ? 1.85 : 1.0;
                int dano = (int) Math.round((base + variacion) * factorCritico);

                String nombreElemento = (elemento != null && !elemento.isBlank()) ? elemento.toUpperCase() : "ARCANA";

                return CalculoTurnoJugador.builder()
                        .tipoAccion(tipoAccion)
                        .danoCalculado(dano)
                        .esCritico(critico)
                        .curacionHp(0)
                        .recuperacionMp(0)
                        .costoMp(costoMp)
                        .mensajeResultado((critico ? "¡EXPLOSIÓN ELEMENTAL CRÍTICA (" + nombreElemento + ")! "
                                                   : "Descarga de magia " + nombreElemento + ": ") + dano + " de daño.")
                        .build();
            }

            case DEFENDER -> {
                partida.setEnDefensa(true);
                // La postura defensiva concentra energía y recupera 15 MP
                int mpRecuperado = Math.min(partida.getMpMaxHeroe() - partida.getMpActualHeroe(), 15);
                partida.setMpActualHeroe(partida.getMpActualHeroe() + mpRecuperado);

                return CalculoTurnoJugador.builder()
                        .tipoAccion(tipoAccion)
                        .danoCalculado(0)
                        .esCritico(false)
                        .curacionHp(0)
                        .recuperacionMp(mpRecuperado)
                        .costoMp(0)
                        .mensajeResultado("Adoptaste postura defensiva. Daño entrante reducido en 50% y recuperaste +" + mpRecuperado + " MP.")
                        .build();
            }

            case USAR_POCION_HP -> {
                if (partida.getPocionesHpRestantes() <= 0) {
                    throw new IllegalArgumentException("No te quedan pociones de salud.");
                }
                partida.setPocionesHpRestantes(partida.getPocionesHpRestantes() - 1);
                int cantidadCura = Math.min(partida.getHpMaxHeroe() - partida.getHpActualHeroe(), 50);
                partida.setHpActualHeroe(partida.getHpActualHeroe() + cantidadCura);

                return CalculoTurnoJugador.builder()
                        .tipoAccion(tipoAccion)
                        .danoCalculado(0)
                        .esCritico(false)
                        .curacionHp(cantidadCura)
                        .recuperacionMp(0)
                        .costoMp(0)
                        .mensajeResultado("Bebiste una Poción de Vida. Recuperaste +" + cantidadCura + " HP.")
                        .build();
            }

            case USAR_POCION_MP -> {
                if (partida.getPocionesMpRestantes() <= 0) {
                    throw new IllegalArgumentException("No te quedan pociones de maná.");
                }
                partida.setPocionesMpRestantes(partida.getPocionesMpRestantes() - 1);
                int cantidadRecuperada = Math.min(partida.getMpMaxHeroe() - partida.getMpActualHeroe(), 40);
                partida.setMpActualHeroe(partida.getMpActualHeroe() + cantidadRecuperada);

                return CalculoTurnoJugador.builder()
                        .tipoAccion(tipoAccion)
                        .danoCalculado(0)
                        .esCritico(false)
                        .curacionHp(0)
                        .recuperacionMp(cantidadRecuperada)
                        .costoMp(0)
                        .mensajeResultado("Bebiste un Elixir de Maná. Restauraste +" + cantidadRecuperada + " MP.")
                        .build();
            }

            default -> throw new IllegalArgumentException("Tipo de acción no soportado: " + tipoAccion);
        }
    }

    private void persistirHistorialTurno(PartidaCombate partida,
                                         Usuario usuario,
                                         int turno,
                                         AccionTurnoRequest request,
                                         CalculoTurnoJugador calculoJugador,
                                         RespuestaJefeIA respuestaIa,
                                         EstadoCombateResponse estadoActualizado) {
        try {
            TurnoHistorial historial = TurnoHistorial.builder()
                    .partida(partida)
                    .usuario(usuario)
                    .numeroTurno(turno)
                    .accionJugador(request.getTipoAccion().name())
                    .payloadFrontend(objectMapper.writeValueAsString(request))
                    .calculoJugador(objectMapper.writeValueAsString(calculoJugador))
                    .payloadIa(objectMapper.writeValueAsString(respuestaIa))
                    .estadoCombateResultante(objectMapper.writeValueAsString(estadoActualizado))
                    .build();

            turnoHistorialRepository.save(historial);
            log.debug("Turno #{} persistido exitosamente para partida #{}", turno, partida.getId());
        } catch (JsonProcessingException e) {
            log.error("Error al serializar historial de turno a JSON: {}", e.getMessage());
        }
    }

    @Transactional(readOnly = true)
    public EstadoCombateResponse obtenerEstadoPartida(Long partidaId) {
        PartidaCombate partida = partidaRepository.findById(partidaId)
                .orElseThrow(() -> new IllegalArgumentException("Partida no encontrada con ID: " + partidaId));
        return mapearAEstadoCombate(partida);
    }

    @Transactional(readOnly = true)
    public List<TurnoHistorialResponse> obtenerHistorialTurnos(Long partidaId) {
        List<TurnoHistorial> turnos = turnoHistorialRepository.findByPartidaIdOrderByNumeroTurnoAsc(partidaId);
        return turnos.stream().map(t -> {
            Object pf = deserializarSeguro(t.getPayloadFrontend());
            Object cj = deserializarSeguro(t.getCalculoJugador());
            Object pia = deserializarSeguro(t.getPayloadIa());
            Object ec = deserializarSeguro(t.getEstadoCombateResultante());

            return TurnoHistorialResponse.builder()
                    .id(t.getId())
                    .partidaId(t.getPartida().getId())
                    .usuarioId(t.getUsuario().getId())
                    .numeroTurno(t.getNumeroTurno())
                    .accionJugador(t.getAccionJugador())
                    .payloadFrontend(pf)
                    .calculoJugador(cj)
                    .payloadIa(pia)
                    .estadoCombateResultante(ec)
                    .createdAt(t.getCreatedAt())
                    .build();
        }).toList();
    }

    private Object deserializarSeguro(String json) {
        try {
            return objectMapper.readValue(json, Object.class);
        } catch (Exception e) {
            return json;
        }
    }

    public EstadoCombateResponse mapearAEstadoCombate(PartidaCombate partida) {
        return EstadoCombateResponse.builder()
                .partidaId(partida.getId())
                .usuarioId(partida.getUsuario().getId())
                .username(partida.getUsuario().getUsername())
                .clasePersonaje(partida.getUsuario().getClasePersonaje())
                .hpHeroe(partida.getHpActualHeroe())
                .hpMaxHeroe(partida.getHpMaxHeroe())
                .mpHeroe(partida.getMpActualHeroe())
                .mpMaxHeroe(partida.getMpMaxHeroe())
                .nombreJefe(partida.getNombreJefe())
                .tipoJefe(partida.getTipoJefe())
                .hpJefe(partida.getHpActualJefe())
                .hpMaxJefe(partida.getHpMaxJefe())
                .turnoActual(partida.getTurnoActual())
                .pocionesHpRestantes(partida.getPocionesHpRestantes())
                .pocionesMpRestantes(partida.getPocionesMpRestantes())
                .enDefensa(partida.getEnDefensa())
                .estado(partida.getEstado())
                .danoTotalInfligido(partida.getDanoTotalInfligido())
                .danoTotalRecibido(partida.getDanoTotalRecibido())
                .build();
    }
}
