package elinps

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"time"

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

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

type Store struct {
	pool *pgxpool.Pool
}

func NewStore(pool *pgxpool.Pool) *Store { return &Store{pool: pool} }

func (s *Store) poolRef() *pgxpool.Pool { return s.pool }

// ------------------------------
// Painel: comentários e status de análise
// ------------------------------

type StatusAnalisePainel = contratos.StatusAnalisePainel

const (
	StatusAnalisePendente  = contratos.StatusAnalisePendente
	StatusAnaliseConcluida = contratos.StatusAnaliseConcluida
)

type ComentarioPainel = contratos.ComentarioPainel

// GetStatusAnalise retorna o status do painel para uma resposta.
// Se não existir registro, considera "pendente".
func (s *Store) GetStatusAnalise(ctx context.Context, produto, respostaID string) (StatusAnalisePainel, error) {
	var status string
	err := s.pool.QueryRow(ctx, `
SELECT status
FROM painel_resposta_status
WHERE produto=$1 AND resposta_id=$2
`, produto, respostaID).Scan(&status)
	if err == pgx.ErrNoRows {
		return StatusAnalisePendente, nil
	}
	if err != nil {
		return "", err
	}
	switch status {
	case string(StatusAnalisePendente):
		return StatusAnalisePendente, nil
	case string(StatusAnaliseConcluida):
		return StatusAnaliseConcluida, nil
	default:
		// fallback defensivo
		return StatusAnalisePendente, nil
	}
}

func (s *Store) SetStatusAnalise(ctx context.Context, produto, respostaID string, status StatusAnalisePainel) error {
	if status != StatusAnalisePendente && status != StatusAnaliseConcluida {
		return fmt.Errorf("status invalido")
	}
	concluidaEm := (*time.Time)(nil)
	if status == StatusAnaliseConcluida {
		agora := time.Now()
		concluidaEm = &agora
	}

	_, err := s.pool.Exec(ctx, `
INSERT INTO painel_resposta_status (produto, resposta_id, status, concluida_em, atualizado_em)
VALUES ($1,$2,$3,$4, now())
ON CONFLICT (produto, resposta_id)
DO UPDATE SET
  status=EXCLUDED.status,
  concluida_em=EXCLUDED.concluida_em,
  atualizado_em=now()
`, produto, respostaID, string(status), concluidaEmOrNil(concluidaEm))
	return err
}

func concluidaEmOrNil(t *time.Time) any {
	if t == nil {
		return nil
	}
	return *t
}

func (s *Store) ListarComentariosPainel(ctx context.Context, produto, respostaID string) ([]ComentarioPainel, error) {
	rows, err := s.pool.Query(ctx, `
SELECT id::text, pessoa_nome, sessao_id::text, comentario, criado_em, atualizado_em
FROM painel_resposta_comentario
WHERE produto=$1 AND resposta_id=$2
ORDER BY criado_em ASC
`, produto, respostaID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	out := []ComentarioPainel{}
	for rows.Next() {
		var c ComentarioPainel
		if err := rows.Scan(&c.ID, &c.PessoaNome, &c.SessaoID, &c.Comentario, &c.CriadoEm, &c.AtualizadoEm); err != nil {
			return nil, err
		}
		out = append(out, c)
	}
	return out, rows.Err()
}

func (s *Store) CriarComentarioPainel(ctx context.Context, produto, respostaID, pessoaNome, sessaoID, comentario string) error {
	_, err := s.pool.Exec(ctx, `
INSERT INTO painel_resposta_comentario (produto, resposta_id, pessoa_nome, sessao_id, comentario, criado_em, atualizado_em)
VALUES ($1,$2,$3,$4::uuid,$5, now(), now())
`, produto, respostaID, pessoaNome, sessaoID, comentario)
	return err
}

// EditarComentarioPainel edita um comentário, mas somente se pertencer à mesma sessão.
func (s *Store) EditarComentarioPainel(ctx context.Context, comentarioID, sessaoID, comentario string) (bool, error) {
	tag, err := s.pool.Exec(ctx, `
UPDATE painel_resposta_comentario
SET comentario=$3, atualizado_em=now()
WHERE id=$1::uuid AND sessao_id=$2::uuid
`, comentarioID, sessaoID, comentario)
	if err != nil {
		return false, err
	}
	return tag.RowsAffected() > 0, nil
}

// DeletarComentarioPainel remove um comentário, mas somente se pertencer à mesma sessão.
func (s *Store) DeletarComentarioPainel(ctx context.Context, comentarioID, sessaoID string) (bool, error) {
	tag, err := s.pool.Exec(ctx, `
DELETE FROM painel_resposta_comentario
WHERE id=$1::uuid AND sessao_id=$2::uuid
`, comentarioID, sessaoID)
	if err != nil {
		return false, err
	}
	return tag.RowsAffected() > 0, nil
}

// GetStatusAnaliseBatch retorna um mapa resposta_id -> status.
// Se não existir registro para um id, ele simplesmente não aparece no mapa.
func (s *Store) GetStatusAnaliseBatch(ctx context.Context, produto string, respostaIDs []string) (map[string]StatusAnalisePainel, error) {
	out := map[string]StatusAnalisePainel{}
	if len(respostaIDs) == 0 {
		return out, nil
	}

	rows, err := s.pool.Query(ctx, `
SELECT resposta_id, status
FROM painel_resposta_status
WHERE produto=$1 AND resposta_id = ANY($2::text[])
`, produto, respostaIDs)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	for rows.Next() {
		var id, st string
		if err := rows.Scan(&id, &st); err != nil {
			return nil, err
		}
		switch st {
		case string(StatusAnaliseConcluida):
			out[id] = StatusAnaliseConcluida
		default:
			out[id] = StatusAnalisePendente
		}
	}
	return out, rows.Err()
}

// ListarComentariosPainelBatch retorna um mapa resposta_id -> comentários.
func (s *Store) ListarComentariosPainelBatch(ctx context.Context, produto string, respostaIDs []string) (map[string][]ComentarioPainel, error) {
	out := map[string][]ComentarioPainel{}
	if len(respostaIDs) == 0 {
		return out, nil
	}

	rows, err := s.pool.Query(ctx, `
SELECT resposta_id, id::text, pessoa_nome, sessao_id::text, comentario, criado_em, atualizado_em
FROM painel_resposta_comentario
WHERE produto=$1 AND resposta_id = ANY($2::text[])
ORDER BY resposta_id ASC, criado_em ASC
`, produto, respostaIDs)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	for rows.Next() {
		var respostaID string
		var c ComentarioPainel
		if err := rows.Scan(&respostaID, &c.ID, &c.PessoaNome, &c.SessaoID, &c.Comentario, &c.CriadoEm, &c.AtualizadoEm); err != nil {
			return nil, err
		}
		out[respostaID] = append(out[respostaID], c)
	}
	return out, rows.Err()
}

func ipReal(r *http.Request) string {
	// IP real do cliente.
	//
	// Importante:
	// - No servidor, usamos middleware.RealIP (chi) que resolve o IP considerando
	//   headers comuns de proxy (X-Forwarded-For / X-Real-IP).
	// - Aqui usamos o r.RemoteAddr já processado e extraímos apenas o host.
	// - Se não for possível parsear, retornamos vazio.
	ip := r.RemoteAddr
	if host, _, err := net.SplitHostPort(ip); err == nil {
		ip = host
	}
	if net.ParseIP(ip) == nil {
		return ""
	}
	return ip
}

func (s *Store) EnsureTableForProduto(ctx context.Context, produtoNome string) (table string, err error) {
	prod, err := db.NormalizeProduto(produtoNome)
	if err != nil {
		return "", err
	}
	table = db.TableNameForProduto(prod)
	if err := db.EnsureNPSTable(ctx, s.pool, table); err != nil {
		return "", err
	}
	return table, nil
}

func (s *Store) HasRespostaValidaRecente(ctx context.Context, table, inquilinoCodigo, usuarioCodigo string) (bool, error) {
	// Segurança: a tabela é um identificador interpolado. Validamos sempre.
	if !db.TableNameValido(table) {
		return false, fmt.Errorf("tabela invalida")
	}
	q := fmt.Sprintf(`
SELECT 1
FROM %s
WHERE inquilino_codigo=$1 AND usuario_codigo=$2
  AND status='respondido' AND valida=true
  AND respondido_em >= now() - interval '45 days'
LIMIT 1`, table)

	var one int
	err := s.pool.QueryRow(ctx, q, inquilinoCodigo, usuarioCodigo).Scan(&one)
	if err == pgx.ErrNoRows {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return true, nil
}

func (s *Store) HasPedidoEmAbertoRecente(ctx context.Context, table, inquilinoCodigo, usuarioCodigo string) (bool, error) {
	// Segurança: a tabela é um identificador interpolado. Validamos sempre.
	if !db.TableNameValido(table) {
		return false, fmt.Errorf("tabela invalida")
	}
	q := fmt.Sprintf(`
SELECT 1
FROM %s
WHERE inquilino_codigo=$1 AND usuario_codigo=$2
  AND status='pedido'
  AND pedido_criado_em >= now() - interval '10 days'
LIMIT 1`, table)
	var one int
	err := s.pool.QueryRow(ctx, q, inquilinoCodigo, usuarioCodigo).Scan(&one)
	if err == pgx.ErrNoRows {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return true, nil
}

func (s *Store) CreatePedido(ctx context.Context, table string, in contratos.PedidoInput, r *http.Request) (string, error) {
	// Segurança: a tabela é um identificador interpolado. Validamos sempre.
	if !db.TableNameValido(table) {
		return "", fmt.Errorf("tabela invalida")
	}
	q := fmt.Sprintf(`
INSERT INTO %s (
	  produto_nome,
  inquilino_codigo, inquilino_nome,
  usuario_codigo, usuario_nome, usuario_email, usuario_telefone,
  status, origem, user_agent, ip_real
) VALUES ($1,$2,$3,$4,$5,$6,$7,'pedido','widget_iframe',$8,$9)
RETURNING id`, table)

	var id string
	err := s.pool.QueryRow(ctx, q,
		in.ProdutoNome,
		in.InquilinoCodigo, in.InquilinoNome,
		in.UsuarioCodigo, in.UsuarioNome, in.UsuarioEmail, in.UsuarioTelefone,
		r.UserAgent(), ipReal(r),
	).Scan(&id)
	return id, err
}

func (s *Store) GetRegistro(ctx context.Context, table, id string) (contratos.Registro, error) {
	// Segurança: a tabela é um identificador interpolado. Validamos sempre.
	if !db.TableNameValido(table) {
		return contratos.Registro{}, fmt.Errorf("tabela invalida")
	}
	q := fmt.Sprintf(`
SELECT id, produto_nome, status, nota, justificativa, pedido_criado_em, respondido_em
FROM %s
WHERE id=$1`, table)

	var reg contratos.Registro
	err := s.pool.QueryRow(ctx, q, id).Scan(
		&reg.ID, &reg.ProdutoNome, &reg.Status, &reg.Nota, &reg.Justificativa, &reg.PedidoCriadoEm, &reg.RespondidoEm,
	)
	return reg, err
}

func (s *Store) PatchRegistro(ctx context.Context, table, id string, in contratos.PatchInput) error {
	// Segurança: a tabela é um identificador interpolado. Validamos sempre.
	if !db.TableNameValido(table) {
		return fmt.Errorf("tabela invalida")
	}
	// UPDATE único com campos opcionais.
	q := fmt.Sprintf(`
UPDATE %s
SET
  nota = COALESCE($2, nota),
  justificativa = COALESCE($3, justificativa),
  status = CASE WHEN $4 THEN 'respondido' ELSE status END,
  respondido_em = CASE WHEN $4 THEN COALESCE(respondido_em, now()) ELSE respondido_em END,
  atualizado_em = now()
WHERE id=$1`, table)

	_, err := s.pool.Exec(ctx, q, id, in.Nota, in.Justificativa, in.Finalizar)
	return err
}

func (s *Store) TouchAtualizadoEm(ctx context.Context, table, id string) error {
	// Segurança: a tabela é um identificador interpolado. Validamos sempre.
	if !db.TableNameValido(table) {
		return fmt.Errorf("tabela invalida")
	}
	q := fmt.Sprintf(`UPDATE %s SET atualizado_em=now() WHERE id=$1`, table)
	_, err := s.pool.Exec(ctx, q, id)
	return err
}

func (s *Store) CooldownSuggested(reg contratos.Registro) time.Duration {
	// Não é usado pelo servidor hoje; fica como helper se precisarmos.
	if reg.Status == "respondido" {
		return 45 * 24 * time.Hour
	}
	return 24 * time.Hour
}
