package elinps

import (
	"encoding/json"
	"errors"
	"log/slog"
	"net/http"
	"strings"

	"e-li.nps/internal/contratos"
	"e-li.nps/internal/db"

	"github.com/go-chi/chi/v5"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

type Handlers struct {
	store *Store
	tpl   *TemplateRenderer
}

func NewHandlers(pool *pgxpool.Pool) *Handlers {
	return &Handlers{
		store: NewStore(pool),
		tpl:   NewTemplateRenderer(mustParseTemplates()),
	}
}

func (h *Handlers) PostPedido(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	var in contratos.PedidoInput
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "json_invalido"})
		return
	}
	if err := ValidatePedidoInput(&in); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}

	// Garante a tabela do produto (e normaliza o identificador técnico).
	table, err := h.store.EnsureTableForProduto(ctx, in.ProdutoNome)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "produto_invalido"})
		return
	}

	// Mantemos a forma normalizada para o widget montar URLs com segurança.
	// table = "nps_" + produto_normalizado
	produtoNormalizado := strings.TrimPrefix(table, "nps_")

	// Regras.
	respRecente, err := h.store.HasRespostaValidaRecente(ctx, table, in.InquilinoCodigo, in.UsuarioCodigo)
	if err != nil {
		slog.Error("erro ao checar resposta recente", "err", err)
		// Fail-closed.
		writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "erro"})
		return
	}
	if respRecente {
		writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "resposta_recente"})
		return
	}

	pedidoAberto, err := h.store.HasPedidoEmAbertoRecente(ctx, table, in.InquilinoCodigo, in.UsuarioCodigo)
	if err != nil {
		slog.Error("erro ao checar pedido em aberto", "err", err)
		writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "erro"})
		return
	}
	if pedidoAberto {
		writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "pedido_em_aberto"})
		return
	}

	id, err := h.store.CreatePedido(ctx, table, in, r)
	if err != nil {
		slog.Error("erro ao criar pedido", "err", err)
		writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "erro"})
		return
	}

	writeJSON(w, http.StatusOK, map[string]any{"pode_abrir": true, "id": id, "produto": produtoNormalizado})
}

func (h *Handlers) PatchResposta(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	produtoParam := chi.URLParam(r, "produto")
	id := chi.URLParam(r, "id")

	// produtoParam já está no path; sanitizamos novamente por segurança.
	prod, err := db.NormalizeProduto(produtoParam)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "produto_invalido"})
		return
	}
	table := db.TableNameForProduto(prod)
	if err := db.EnsureNPSTable(ctx, h.store.pool, table); err != nil {
		slog.Error("erro ao garantir tabela", "err", err)
		writeJSON(w, http.StatusInternalServerError, map[string]any{"error": "db"})
		return
	}

	var in contratos.PatchInput
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "json_invalido"})
		return
	}
	if err := ValidatePatchInput(&in); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}

	if in.Nota == nil && in.Justificativa == nil && !in.Finalizar {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "nada_para_atualizar"})
		return
	}

	if err := h.store.PatchRegistro(ctx, table, id, in); err != nil {
		slog.Error("erro ao atualizar registro", "err", err)
		writeJSON(w, http.StatusInternalServerError, map[string]any{"error": "db"})
		return
	}

	// Se chamado via HTMX, respondemos com fragmento HTML atualizado.
	if r.Header.Get("HX-Request") == "true" {
		reg, err := h.store.GetRegistro(ctx, table, id)
		if err != nil {
			slog.Error("erro ao buscar registro", "err", err)
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte("db"))
			return
		}
		data := contratos.FormPageData{Produto: prod, ID: id, Reg: reg}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		h.tpl.Render(w, "form_inner.html", data)
		return
	}

	writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}

func (h *Handlers) GetForm(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	produtoParam := chi.URLParam(r, "produto")
	id := chi.URLParam(r, "id")

	prod, err := db.NormalizeProduto(produtoParam)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("produto invalido"))
		return
	}
	table := db.TableNameForProduto(prod)
	if err := db.EnsureNPSTable(ctx, h.store.pool, table); err != nil {
		slog.Error("erro ao garantir tabela", "err", err)
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("db"))
		return
	}

	reg, err := h.store.GetRegistro(ctx, table, id)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte("nao encontrado"))
			return
		}
		slog.Error("erro ao buscar registro", "err", err)
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("db"))
		return
	}

	data := contratos.FormPageData{
		Produto: prod,
		ID:      id,
		Reg:     reg,
	}

	// Sempre retornamos uma página HTML completa para o widget usar iframe.
	// Porém o container interno também é compatível com HTMX (swap de si mesmo).
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	h.tpl.Render(w, "form_page.html", data)
}

func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(v)
}
