package db

import (
	"context"
	"fmt"
	"regexp"
	"strings"
	"unicode"

	"github.com/jackc/pgx/v5/pgxpool"
	"golang.org/x/text/unicode/norm"
)

var produtoRe = regexp.MustCompile(`^[a-z_][a-z0-9_]*$`)

// TableNameValido valida se o nome de tabela está no formato esperado do projeto:
// "nps_" + produto normalizado.
//
// Motivação (segurança): identificadores não podem ser parametrizados em SQL.
// Então, sempre que precisarmos interpolar um nome de tabela, validamos aqui
// para evitar SQL injection via identificador.
func TableNameValido(tableName string) bool {
	if !strings.HasPrefix(tableName, "nps_") {
		return false
	}
	produto := strings.TrimPrefix(tableName, "nps_")
	// produtoRe já garante: ^[a-z_][a-z0-9_]*$
	// (e a normalização limita tamanho em NormalizeProduto).
	return produtoRe.MatchString(produto)
}

// NormalizeProduto normaliza e valida um nome de produto para uso em:
// - nomes de tabela no Postgres (prefixo nps_)
// - rotas/URLs (parâmetro {produto})
//
// Regras:
// - minúsculo + trim
// - remove diacríticos
// - converte qualquer caractere fora de [a-z0-9_] para '_'
// - colapsa '_' repetidos
// - valida contra regex e tamanho máximo de identificador
//
// Importante: isso NÃO é usado para exibição ao usuário.
func NormalizeProduto(produtoNome string) (string, error) {
	p := strings.ToLower(strings.TrimSpace(produtoNome))
	if p == "" {
		return "", fmt.Errorf("produto invalido")
	}

	// Remove diacritics (NFD + strip marks)
	p = norm.NFD.String(p)
	p = strings.Map(func(r rune) rune {
		if unicode.Is(unicode.Mn, r) {
			return -1
		}
		return r
	}, p)

	// Replace anything not allowed with underscore
	p = strings.Map(func(r rune) rune {
		switch {
		case r >= 'a' && r <= 'z':
			return r
		case r >= '0' && r <= '9':
			return r
		case r == '_':
			return r
		default:
			return '_'
		}
	}, p)

	// Collapse underscores
	for strings.Contains(p, "__") {
		p = strings.ReplaceAll(p, "__", "_")
	}
	p = strings.Trim(p, "_")

	// Postgres identifiers are max 63 chars. Table name is "nps_" + produto.
	if len(p) > 59 {
		return "", fmt.Errorf("produto invalido")
	}

	if !produtoRe.MatchString(p) {
		return "", fmt.Errorf("produto invalido")
	}
	return p, nil
}

func TableNameForProduto(produto string) string {
	return "nps_" + produto
}

func EnsurePgcrypto(ctx context.Context, pool *pgxpool.Pool) error {
	_, err := pool.Exec(ctx, `CREATE EXTENSION IF NOT EXISTS pgcrypto`)
	return err
}

// EnsureNPSTable cria a tabela por produto + índices se não existirem.
// Importante: tableName deve ser criada a partir de um produto normalizado.
func EnsureNPSTable(ctx context.Context, pool *pgxpool.Pool, tableName string) error {
	// Identifiers cannot be passed as $1 parameters, so we must interpolate.
	// Segurança: tableName deve ser estritamente derivada de NormalizeProduto + prefix.
	if !TableNameValido(tableName) {
		return fmt.Errorf("nome de tabela invalido")
	}
	q := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  -- Nome do produto como informado pela integração/widget.
  -- Importante: NÃO é usado para nome de tabela; é apenas para exibição.
  produto_nome text NOT NULL DEFAULT '',
  inquilino_codigo text NOT NULL,
  inquilino_nome text NOT NULL,
  usuario_codigo text,
  usuario_nome text NOT NULL,
  usuario_email text,
  usuario_telefone text,
  status text NOT NULL CHECK (status IN ('pedido','respondido')),
  pedido_criado_em timestamptz NOT NULL DEFAULT now(),
  respondido_em timestamptz NULL,
  atualizado_em timestamptz NOT NULL DEFAULT now(),
  nota int NULL CHECK (nota BETWEEN 1 AND 10),
  justificativa text NULL,
  valida bool NOT NULL DEFAULT true,
  origem text NOT NULL DEFAULT 'widget_iframe',
  user_agent text NULL,
  -- IP real do usuário (após middleware RealIP). Pode conter IPv4 ou IPv6.
  -- Importante: quando rodar atrás de proxy (ex.: Docker + Nginx/Traefik),
  -- garanta que o proxy repasse X-Forwarded-For/X-Real-IP.
  ip_real text NULL
);

ALTER TABLE %s ADD COLUMN IF NOT EXISTS usuario_codigo text;
ALTER TABLE %s ADD COLUMN IF NOT EXISTS produto_nome text NOT NULL DEFAULT '';
ALTER TABLE %s ADD COLUMN IF NOT EXISTS ip_real text;

-- NOTE: controle de exibição é por (produto + inquilino_codigo + usuario_codigo)
-- então os índices são baseados em usuario_codigo.

CREATE INDEX IF NOT EXISTS idx_nps_resp_recente_%s
  ON %s (inquilino_codigo, usuario_codigo, respondido_em DESC)
  WHERE status='respondido' AND valida=true;

CREATE INDEX IF NOT EXISTS idx_nps_pedido_aberto_%s
  ON %s (inquilino_codigo, usuario_codigo, pedido_criado_em DESC)
  WHERE status='pedido';
`, tableName, tableName, tableName, tableName, tableName, tableName, tableName, tableName)

	_, err := pool.Exec(ctx, q)
	return err
}
