package render

import (
	"fmt"
	"html/template"
	"io/fs"
	"net/http"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"leadb2b.org/internal/api"
)

type Engine struct {
	templates map[string]*template.Template
	funcMap   template.FuncMap
	siteURL   string
	mu        sync.RWMutex
}

type PageMeta struct {
	Title       string
	Description string
	Canonical   string
	OGType      string
	OGImage     string
	JSONLD      template.HTML
	NoIndex     bool
	PrevURL     string
	NextURL     string
	MinimalAssets bool
}

type HeroCrumb struct {
	Label string
	Href  string
}

type SiteData struct {
	Meta          PageMeta
	HeaderStyle   string
	Skin          string
	MinimalAssets bool
	IsAppPanel    bool
	HeroTitle     string
	HeroCrumbs    []HeroCrumb
}

func NewEngine(siteURL string, templateFS fs.FS) (*Engine, error) {
	funcMap := template.FuncMap{
		"static": func(path string) string {
			if strings.HasPrefix(path, "/") {
				return "/static" + path
			}
			return "/static/" + path
		},
		"fmtCNPJ":           FmtCNPJ,
		"displayCNPJ":       DisplayCNPJForInput,
		"formatCEP":         FormatCEPDisplay,
		"listBadge":         ListBadge,
		"listContactBadges": ListContactBadges,
		"maskedContactBadges": MaskedContactBadges,
		"maskedRowCNPJ":       MaskedCompanyCNPJ,
		"buscaRowCNPJ": func(i int, rowCNPJs []string, item api.MaskedCompanyOut) string {
			if i >= 0 && i < len(rowCNPJs) && rowCNPJs[i] != "" {
				return rowCNPJs[i]
			}
			return MaskedCompanyCNPJ(item)
		},
		"fantasiaOrDash":    FantasiaOrDash,
		"hasText":           HasText,
		"hasTextStr":        HasTextStr,
		"cnaeShort":         CNAEShort,
		"dataAtividade":     DataAtividadeHTML,
		"dataAtividadeSide": DataAtividadeSidebar,
		"selected":          Selected,
		"checked":           Checked,
		"situacaoChecked":        SituacaoChecked,
		"situacaoSelectSelected": SituacaoSelectSelected,
		"unidadeRadioChecked": UnidadeRadioChecked,
		"eq":                func(a, b any) bool { return fmt.Sprint(a) == fmt.Sprint(b) },
		"le":                func(a, b int) bool { return a <= b },
		"ge":                func(a, b int) bool { return a >= b },
		"gt":                func(a, b int) bool { return a > b },
		"join":              strings.Join,
		"add":               func(a, b int) int { return a + b },
		"yearRange":         YearRange,
		"meses":             Meses,
		"defaultCNAEs":      DefaultCNAEs,
		"buildQuery":        BuildQueryFromMap,
		"empresaURL":        EmpresaPathFromList,
		"empresaPremiumURL": func(d *api.EstabelecimentoDetailOut) string { return EmpresaPathFromDetail(d, true) },
		"empresaFAQ": func(d *api.EstabelecimentoDetailOut, locked bool) []FAQItem {
			return BuildEmpresaFAQ(d, locked)
		},
		"safeHTML":          func(s string) template.HTML { return template.HTML(s) },
		"formatCapital":     FormatCapital,
		"sociosNomes":       SociosNomes,
		"socioNome":         SocioNome,
		"buscaSocioURL":     BuscaSocioURL,
		"buscaCNAEURL":      BuscaCNAEURL,
		"hasSocios":         HasSocios,
		"hasTelefone":       HasTelefone,
		"hasEmail":          HasEmail,
		"hasTelefoneDetail": HasTelefoneDetail,
		"hasEmailDetail":    HasEmailDetail,
		"dict":              Dict,
		"inList":            InList,
		"radioChecked":      RadioChecked,
		"optionSelected":    OptionSelected,
		"empresaMapAddress":   EmpresaMapAddress,
		"empresaDisplayAddress": EmpresaDisplayAddress,
		"empresaNomeExibicao":     EmpresaNomeExibicao,
		"empresaNomeExibicaoList": EmpresaNomeExibicaoList,
		"initial": func(s string) string {
			s = strings.TrimSpace(s)
			if s == "" {
				return "U"
			}
			r := []rune(s)
			return strings.ToUpper(string(r[0]))
		},
		"assetVer":          func() string { return "67" },
		"cnaeDescricao":     LookupCNAEDescricao,
		"formatNumber":      FormatNumber,
		"stageLabel":        StageLabel,
		"temperatureLabel":  TemperatureLabel,
		"temperatureClass":  TemperatureClass,
		"leadTagSelected":   LeadTagSelected,
		"derefInt":          DerefInt,
	}

	e := &Engine{
		templates: make(map[string]*template.Template),
		funcMap:   funcMap,
		siteURL:   strings.TrimRight(siteURL, "/"),
	}

	layouts, err := fs.Glob(templateFS, "templates/layouts/*.html")
	if err != nil {
		return nil, err
	}
	pages, err := fs.Glob(templateFS, "templates/pages/*.html")
	if err != nil {
		return nil, err
	}
	partials, err := fs.Glob(templateFS, "templates/partials/*.html")
	if err != nil {
		return nil, err
	}

	for _, page := range pages {
		name := strings.TrimSuffix(filepath.Base(page), ".html")
		files := append([]string{}, layouts...)
		files = append(files, partials...)
		files = append(files, page)
		tmpl, err := template.New(name).Funcs(funcMap).ParseFS(templateFS, files...)
		if err != nil {
			return nil, fmt.Errorf("parse template %s: %w", name, err)
		}
		e.templates[name] = tmpl
	}

	appPages, err := fs.Glob(templateFS, "templates/app/*.html")
	if err != nil {
		return nil, err
	}
	appLayout := "templates/layouts/app.html"
	appPartials := []string{
		"templates/partials/head.html",
		"templates/partials/scripts.html",
		"templates/partials/app-header.html",
		"templates/partials/app-sidebar.html",
		"templates/partials/app-upgrade-banner.html",
	}
	for _, page := range appPages {
		name := strings.TrimSuffix(filepath.Base(page), ".html")
		files := append([]string{appLayout}, appPartials...)
		files = append(files, page)
		tmpl, err := template.New(name).Funcs(funcMap).ParseFS(templateFS, files...)
		if err != nil {
			return nil, fmt.Errorf("parse app template %s: %w", name, err)
		}
		e.templates[name] = tmpl
	}

	return e, nil
}

func (e *Engine) Render(w http.ResponseWriter, name string, data any) error {
	e.mu.RLock()
	tmpl, ok := e.templates[name]
	e.mu.RUnlock()
	if !ok {
		http.Error(w, "template not found", http.StatusInternalServerError)
		return fmt.Errorf("template %s not found", name)
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	return tmpl.ExecuteTemplate(w, "base", data)
}

func (e *Engine) RenderBlock(w http.ResponseWriter, pageName, blockName string, data any) error {
	e.mu.RLock()
	tmpl, ok := e.templates[pageName]
	e.mu.RUnlock()
	if !ok {
		http.Error(w, "template not found", http.StatusInternalServerError)
		return fmt.Errorf("template %s not found", pageName)
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	return tmpl.ExecuteTemplate(w, blockName, data)
}

func (e *Engine) SiteURL() string {
	return e.siteURL
}

func DefaultMeta(title, desc, path string, siteURL string) PageMeta {
	if title == "" {
		title = "LeadB2B | Leads B2B — Dados de empresas"
	}
	if desc == "" {
		desc = "Consulte empresas por segmento, estado e cidade. Dados oficiais da Receita Federal."
	}
	canonical := siteURL
	if path != "" && path != "/" {
		canonical = siteURL + path
	}
	return PageMeta{
		Title:       title,
		Description: desc,
		Canonical:   canonical,
		OGType:      "website",
		OGImage:     OGImageURL(siteURL),
	}
}

func EmpresaJSONLD(d *api.EstabelecimentoDetailOut, siteURL string) template.HTML {
	name := d.Empresa.RazaoSocial
	addr := d.Endereco.EnderecoCompleto
	city := d.Endereco.Municipio.Descricao
	uf := d.Endereco.UF
	cep := d.Endereco.CEP
	cnpj := FmtCNPJ(d.CNPJ)
	url := strings.TrimRight(siteURL, "/") + EmpresaPathFromDetail(d, false)
	ld := fmt.Sprintf(`{"@context":"https://schema.org","@type":"Organization","name":%q,"url":%q,"taxID":%q,"address":{"@type":"PostalAddress","streetAddress":%q,"addressLocality":%q,"addressRegion":%q,"postalCode":%q,"addressCountry":"BR"}}`,
		name, url, cnpj, addr, city, uf, cep)
	return template.HTML(ld)
}

func YearRange() []int {
	years := make([]int, 0, 67)
	for y := time.Now().Year(); y >= 1960; y-- {
		years = append(years, y)
	}
	return years
}

type Mes struct {
	Num   int
	Label string
}

func Meses() []Mes {
	return []Mes{
		{1, "Janeiro"}, {2, "Fevereiro"}, {3, "Março"}, {4, "Abril"},
		{5, "Maio"}, {6, "Junho"}, {7, "Julho"}, {8, "Agosto"},
		{9, "Setembro"}, {10, "Outubro"}, {11, "Novembro"}, {12, "Dezembro"},
	}
}

type CNAEOption struct {
	Code  string
	Label string
}

func DefaultCNAEs() []CNAEOption {
	return []CNAEOption{
		{"6422100", "Bancos múltiplos"},
		{"6201501", "Desenvolvimento de software"},
		{"4711301", "Comércio varejista"},
		{"4120400", "Construção de edifícios"},
		{"8599699", "Ensino e capacitação"},
	}
}

func Selected(current, value string) string {
	if current == value {
		return ` selected`
	}
	return ""
}

func Checked(values []string, value string) string {
	want := fmt.Sprint(value)
	for _, v := range values {
		if fmt.Sprint(v) == want {
			return ` checked`
		}
	}
	return ""
}

// SituacaoChecked reflete as situações aplicadas na busca (incluindo padrão Ativa).
func SituacaoChecked(values []string, codigo any) string {
	return Checked(values, fmt.Sprint(codigo))
}

// SituacaoSelectSelected marca a opção do select de situação cadastral (valor único).
func SituacaoSelectSelected(values []string, codigo any) string {
	code := fmt.Sprint(codigo)
	if len(values) == 0 {
		if code == "" {
			return ` selected`
		}
		return ""
	}
	if len(values) == 1 && values[0] == code {
		return ` selected`
	}
	return ""
}

// UnidadeRadioChecked reflete o tipo de unidade aplicado na busca.
func UnidadeRadioChecked(current, value string) string {
	if current == value {
		return ` checked`
	}
	return ""
}

func BuildQueryString(params map[string]string, extras map[string][]string) string {
	var parts []string
	for k, v := range params {
		if v != "" {
			parts = append(parts, k+"="+template.URLQueryEscaper(v))
		}
	}
	for k, vals := range extras {
		for _, v := range vals {
			if v != "" {
				parts = append(parts, k+"="+template.URLQueryEscaper(v))
			}
		}
	}
	return strings.Join(parts, "&")
}

func Dict(values ...any) (map[string]any, error) {
	if len(values)%2 != 0 {
		return nil, fmt.Errorf("dict: número ímpar de argumentos")
	}
	m := make(map[string]any, len(values)/2)
	for i := 0; i+1 < len(values); i += 2 {
		key, ok := values[i].(string)
		if !ok {
			return nil, fmt.Errorf("dict: chave deve ser string")
		}
		m[key] = values[i+1]
	}
	return m, nil
}

func InList(list []string, value string) bool {
	for _, v := range list {
		if v == value {
			return true
		}
	}
	return false
}

func RadioChecked(current, value string) string {
	if current == value {
		return ` checked`
	}
	return ""
}

func OptionSelected(current, value string) string {
	if current == value {
		return ` selected`
	}
	return ""
}
