package api

import (
	"database/sql"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"producer/internal/kafka"
	"producer/internal/logging"
	"producer/internal/models"
	"producer/pkg/utils"

	"github.com/google/uuid"
	"github.com/gorilla/mux"
)

// APIServer contiene los componentes necesarios para el API
type APIServer struct {
	kc *kafka.KafkaClient
	db *sql.DB
}

// NewAPIServer crea una nueva instancia de APIServer
func NewAPIServer(kc *kafka.KafkaClient, db *sql.DB) *APIServer {
	return &APIServer{kc: kc, db: db}
}

// EnviarFacturaHandler procesa las solicitudes para enviar facturas
func (s *APIServer) EnviarFacturaHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	if ct := r.Header.Get("Content-Type"); ct != "application/json" {
		logging.ErrorLogger.Printf("El Content-Type debe ser application/json")
		writeError(w, ErrInvalidContentType)
		return
	}

	// Get max body size from env var with fallback to 1MB
	maxBodySize := int64(utils.GetEnvInt("API_MAX_BODY_SIZE", 1<<20))
	lr := io.LimitReader(r.Body, maxBodySize)
	dec := json.NewDecoder(lr)
	dec.DisallowUnknownFields()

	var req models.RequestFacturacionModel
	if err := dec.Decode(&req); err != nil {
		logging.ErrorLogger.Printf("El formato JSON enviado es inválido")
		var apiErr = ErrInvalidJSON
		apiErr.Data = err.Error()
		writeError(w, apiErr)
		return
	}

	if err := dec.Decode(&struct{}{}); err != io.EOF {
		logging.ErrorLogger.Printf("El JSON contiene contenido extra después del objeto principal: %v", err)
		writeError(w, ErrJSONExtraContent)
		return
	}

	transID := uuid.New().String()
	msg := models.FacturaEventModel{
		TransaccionID: transID,
		Estado:        "PENDIENTE",
		Payload:       req,
	}

	payload, err := json.Marshal(msg)
	if err != nil {
		logging.ErrorLogger.Printf("Error al serializar mensaje: %v", err)
		writeError(w, ErrInternal)
		return
	}

	facturacionTopic := utils.GetEnv("KAFKA_TOPIC_FACTURACION", "FACTURACION")
	if err := s.kc.Publish(r.Context(), facturacionTopic, transID, payload); err != nil {
		logging.ErrorLogger.Printf("Error al publicar en %s: %v", facturacionTopic, err)
		writeError(w, ErrKafkaPublish)
		return
	}

	logging.InfoLogger.Printf("Factura enviada: %s", transID)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)

	json.NewEncoder(w).Encode(models.ApiResponse{
		Success:       true,
		Status:        200,
		TransaccionID: transID,
		Message:       "processing",
	})
}

// RevertirFacturaHandler procesa las solicitudes para revertir facturas
func (s *APIServer) RevertirFacturaHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	var req models.FacturaEventModel
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		logging.ErrorLogger.Printf("Error al decodificar payload: %v", err)
		var apiErr = ErrInvalidJSON
		apiErr.Data = err.Error()
		writeError(w, apiErr)
		return
	}

	if req.TransaccionID == "" {
		logging.ErrorLogger.Printf("El transaccion_id es requerido")
		writeError(w, ErrMissingTransactionID)
		return
	}

	// Validar que el ID sea un UUID válido
	if _, err := uuid.Parse(req.TransaccionID); err != nil {
		logging.ErrorLogger.Printf("El ID proporcionado no es un UUID válido")
		writeError(w, ErrInvalidUUID)
		return
	}

	req.Estado = "REPROCESAR"
	payload, err := json.Marshal(req)
	if err != nil {
		logging.ErrorLogger.Printf("Error al serializar mensaje: %v", err)
		writeError(w, ErrInternal)
		return
	}

	facturacionTopic := utils.GetEnv("KAFKA_TOPIC_FACTURACION", "FACTURACION")
	if err := s.kc.Publish(r.Context(), facturacionTopic, req.TransaccionID, payload); err != nil {
		logging.ErrorLogger.Printf("Error al publicar en mensaje en Kafka %s: %v", facturacionTopic, err)
		writeError(w, ErrKafkaPublish)
		return
	}

	logging.InfoLogger.Printf("Solicitud de reversión enviada: %s", req.TransaccionID)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)

	json.NewEncoder(w).Encode(models.ApiResponse{
		Success:       true,
		Status:        200,
		TransaccionID: req.TransaccionID,
		Message:       "error",
		Data:          []models.DataItem{{Msg: "Solicitud de reversión enviada"}},
	})
}

// ConsultarFacturaHandler procesa las solicitudes para consultar facturas
func (s *APIServer) ConsultarFacturaHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]
	if id == "" {
		logging.ErrorLogger.Printf("El transaccion_id es requerido")
		writeError(w, ErrMissingTransactionID)
		return
	}

	if _, err := uuid.Parse(id); err != nil {
		logging.ErrorLogger.Printf("El ID proporcionado no es un UUID válido")
		writeError(w, ErrInvalidUUID)
		return
	}

	var f models.Factura
	var codeAuth sql.NullString
	var fechaCreacion sql.NullTime
	var msgRespSoap sql.NullString

	row := s.db.QueryRowContext(r.Context(), `
		SELECT id, fecha_emision, estado, cuf, url, codigo_documento_sector, fecha_creacion, mensaje_respuesta_soap
		FROM facturacion_facturas
		WHERE id = $1
	`, id)

	err := row.Scan(&f.ID, &f.FechaEmision, &f.Estado, &f.Cuf, &f.UrlFactura, &codeAuth, &fechaCreacion, &msgRespSoap)

	if errors.Is(err, sql.ErrNoRows) {
		logging.ErrorLogger.Printf("La factura solicitada no existe")
		writeError(w, ErrFacturaNotFound)
		return
	} else if err != nil {
		logging.ErrorLogger.Printf("Error al consultar BD: %v", err)
		writeError(w, ErrDatabaseQuery)
		return
	}

	if codeAuth.Valid {
		f.CodigoAutorizacion = codeAuth.String
	}
	if fechaCreacion.Valid {
		f.FechaCreacion = fechaCreacion.Time
	}

	var (
		success bool
		status  int
		message string
		data    []models.DataItem
	)

	// parsear el JSON de entrada
	var mensajes []models.MensajeSOAP
	if err := json.Unmarshal([]byte(msgRespSoap.String), &mensajes); err != nil {
		logging.ErrorLogger.Printf("Error parsing JSON de entrada", err)
	}

	// extraer sólo las descripciones
	msgData := make([]models.DataItem, len(mensajes))
	for i, m := range mensajes {
		msgData[i] = models.DataItem{Msg: m.Descripcion}
	}

	switch f.Estado {
	case "FINALIZADA":
		success = true
		status = http.StatusOK
		message = "done"
		data = []models.DataItem{{CodigoAutorizacion: f.Cuf, URL: f.UrlFactura}}
	case "ANULADA":
		success = true
		status = http.StatusOK
		message = "canceled"
		//data = []models.DataItem{}
		data = msgData
	default:
		success = false
		status = http.StatusBadRequest
		message = "error"
		data = msgData
	}

	estadoEvt := models.ApiResponse{
		Success: success,
		Status:  status,
		Message: message,
		Data:    data,
	}

	// Si no está finalizado, publicar el estado en Kafka
	if f.Estado != "FINALIZADA" && f.Estado != "ANULADA" {
		if payload, err := json.Marshal(estadoEvt); err != nil {
			logging.ErrorLogger.Printf("Error al serializar estado: %v", err)
		} else {
			factEstadoTopic := utils.GetEnv("KAFKA_TOPIC_FACTURACION_ESTADO", "FACTURACION_ESTADO")
			if err := s.kc.Publish(r.Context(), factEstadoTopic, f.ID, payload); err != nil {
				logging.ErrorLogger.Printf("Error al publicar en %s: %v", factEstadoTopic, err)
			} else {
				logging.InfoLogger.Printf("Estado enviado: %s (%s)", f.ID, f.Estado)
			}
		}
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(estadoEvt)
}

// AnularFacturaHandler procesa las solicitudes para anular facturas
func (s *APIServer) AnularFacturaHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	var req struct {
		TransaccionID string `json:"transaccion_id"`
		CodigoMotivo  int    `json:"codigo_motivo"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		logging.ErrorLogger.Printf("Error decoding payload: %v", err)
		var apiErr = ErrInvalidJSON
		apiErr.Data = err.Error()
		writeError(w, apiErr)
		return
	}

	if req.TransaccionID == "" {
		logging.ErrorLogger.Printf("transaccion_id is required")
		writeError(w, ErrMissingTransactionID)
		return
	}

	// Validate that ID is a valid UUID
	if _, err := uuid.Parse(req.TransaccionID); err != nil {
		logging.ErrorLogger.Printf("ID provided is not a valid UUID")
		writeError(w, ErrInvalidUUID)
		return
	}

	// If no reason code is provided, use default (1)
	if req.CodigoMotivo <= 0 {
		req.CodigoMotivo = 1
	}

	// Create message for Kafka
	payload, err := json.Marshal(req)
	if err != nil {
		logging.ErrorLogger.Printf("Error serializing message: %v", err)
		writeError(w, ErrInternal)
		return
	}

	// Publish to Kafka FACTURACION_ANULAR topic
	factAnularTopic := utils.GetEnv("KAFKA_TOPIC_FACTURACION_ANULAR", "FACTURACION_ANULAR")
	if err := s.kc.Publish(r.Context(), factAnularTopic, req.TransaccionID, payload); err != nil {
		logging.ErrorLogger.Printf("Error publishing message to Kafka %s: %v", factAnularTopic, err)
		writeError(w, ErrKafkaPublish)
		return
	}

	logging.InfoLogger.Printf("Cancellation request sent: %s", req.TransaccionID)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)

	json.NewEncoder(w).Encode(models.ApiResponse{
		Success:       true,
		Status:        200,
		TransaccionID: req.TransaccionID,
		Message:       "Done",
	})
}

// ReprocesarDLQHandler procesa las solicitudes para reprocesar mensajes fallidos
func (s *APIServer) ReprocesarDLQHandler(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	// Use environment variable for DLQ processing limit
	dlqLimit := utils.GetEnvInt("DLQ_MAX_PROCESS_LIMIT", 100)

	// Consultar mensajes pendientes de reprocesamiento
	rows, err := s.db.QueryContext(ctx, `
		SELECT id, transaccion_id, mensaje, fecha_creacion, reprocesado
		  FROM facturacion_dlq
		 WHERE reprocesado = false
		 LIMIT $1
	`, dlqLimit)

	if err != nil {
		logging.ErrorLogger.Printf("Error al consultar DLQ: %v", err)
		writeError(w, ErrDatabaseQuery)
		return
	}
	defer rows.Close()

	type ReprocesadoResult struct {
		ID      int    `json:"id"`
		TransID string `json:"transaccion_id"`
		Error   string `json:"error,omitempty"`
	}

	var processed []ReprocesadoResult
	var failed []ReprocesadoResult

	facturacionTopic := utils.GetEnv("KAFKA_TOPIC_FACTURACION", "FACTURACION")

	for rows.Next() {
		var m models.DLQMessage
		if err := rows.Scan(&m.ID, &m.TransaccionID, &m.Mensaje, &m.FechaCreacion, &m.Reprocesado); err != nil {
			logging.ErrorLogger.Printf("Error al escanear registro DLQ: %v", err)
			continue
		}

		var req models.FacturaEventModel
		if err := json.Unmarshal([]byte(m.Mensaje), &req); err != nil {
			logging.ErrorLogger.Printf("Error al parsear mensaje DLQ: %v", err)
			failed = append(failed, ReprocesadoResult{
				ID:      m.ID,
				TransID: m.TransaccionID,
				Error:   "Error al parsear mensaje: " + err.Error(),
			})
			continue
		}

		payload, err := json.Marshal(req)
		if err != nil {
			logging.ErrorLogger.Printf("Error al serializar mensaje: %v", err)
			failed = append(failed, ReprocesadoResult{
				ID:      m.ID,
				TransID: m.TransaccionID,
				Error:   "Error al serializar mensaje: " + err.Error(),
			})
			continue
		}

		if err := s.kc.Publish(ctx, facturacionTopic, req.TransaccionID, payload); err != nil {
			logging.ErrorLogger.Printf("Error al publicar en %s: %v", facturacionTopic, err)
			failed = append(failed, ReprocesadoResult{
				ID:      m.ID,
				TransID: m.TransaccionID,
				Error:   "Error al publicar en Kafka: " + err.Error(),
			})
			continue
		}

		if _, err := s.db.ExecContext(ctx,
			`UPDATE facturacion_dlq SET reprocesado = true WHERE id = $1`, m.ID,
		); err != nil {
			logging.ErrorLogger.Printf("Error al marcar reprocesado: %v", err)
			failed = append(failed, ReprocesadoResult{
				ID:      m.ID,
				TransID: m.TransaccionID,
				Error:   "Error al marcar como reprocesado: " + err.Error(),
			})
			continue
		}

		processed = append(processed, ReprocesadoResult{
			ID:      m.ID,
			TransID: m.TransaccionID,
		})
		logging.InfoLogger.Printf("Reprocesado DLQ: %s", req.TransaccionID)
	}

	// Verificar si hubo error al leer los registros
	if err := rows.Err(); err != nil {
		logging.ErrorLogger.Printf("Error al iterar registros DLQ: %v", err)
	}

	w.Header().Set("Content-Type", "application/json")
	respStatus := "success"

	if len(processed) == 0 && len(failed) > 0 {
		respStatus = "error"
		w.WriteHeader(http.StatusPartialContent)
	} else if len(processed) == 0 {
		respStatus = "empty"
	}

	json.NewEncoder(w).Encode(map[string]interface{}{
		"status":    respStatus,
		"message":   "Reprocesado DLQ",
		"processed": len(processed),
		"success":   processed,
		"failed":    failed,
	})
}
