package handlers

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"

	"leadb2b.org/internal/api"
	"leadb2b.org/internal/product"
	"leadb2b.org/internal/render"
)

type Handler struct {
	leads  *api.LeadsService
	render *render.Engine
	siteURL string
}

func New(leads *api.LeadsService, engine *render.Engine, siteURL string) *Handler {
	return &Handler{leads: leads, render: engine, siteURL: siteURL}
}

type BuscaPage struct {
	render.SiteData
	Items         []api.EstabelecimentoOut
	MaskedItems   []product.MaskedCompany
	UFs           []api.FiltroOpcao
	Situacoes     []api.FiltroOpcao
	Municipios    []api.MunicipioItem
	CNAEs         []render.CNAEOption
	CNAEOptions   []api.CnaeItem
	PorteOptions            []render.PorteOption
	NaturezaJuridicaOptions []render.NaturezaJuridicaOption
	Params                  render.SearchForm
	ActiveFilters []render.ActiveFilterTag
	Pagination    render.Pagination
	SearchNotice  string
	NextCursor    string
	HasMore       bool
	NextPageURL   string
	SociosCounts  map[string]int
}

type EmpresaPage struct {
	render.SiteData
	Detail          *api.EstabelecimentoDetailOut
	Locked          bool
	MapCoords       render.MapCoords
	GeoSummary      string
	SimilarItems    []api.EstabelecimentoOut
}

func (h *Handler) Busca(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	query := r.URL.Query()
	rawParams := render.ParseSearchForm(query)

	if render.IsCNPJLookup(rawParams) {
		if h.redirectCNPJLookup(w, r, rawParams.CNPJ) {
			return
		}
	}

	enrichedParams, cepNotice := render.EnrichSearchFromCEP(ctx, rawParams)
	params := render.ApplySearchDefaults(enrichedParams)
	pageNum := render.ParsePage(query)
	if pageNum == 1 {
		params.Cursor = ""
	}

	meta, err := h.leads.Filtros(ctx)
	if err != nil {
		h.renderError(w, err)
		return
	}

	searchNotice := ""
	var result *api.EstabelecimentoSearchOut
	if render.RequiresUFOrMunicipio(enrichedParams) {
		searchNotice = "Para buscar por nome, razão social ou sócio, selecione UF ou município."
		result = &api.EstabelecimentoSearchOut{}
	} else {
		searchParams := render.ToAPISearchParams(params)
		searchParams.TemTelefone = enrichedParams.TemTelefone
		searchParams.TemEmail = enrichedParams.TemEmail
		searchParams.TemSocios = enrichedParams.TemSocios
		searchParams.MEI = enrichedParams.MEI
		searchParams.Simples = enrichedParams.Simples
		var searchErr error
		result, searchErr = h.searchEstabelecimentos(ctx, searchParams)
		if searchErr != nil {
			h.renderError(w, searchErr)
			return
		}
		if render.NormalizeCEPDigits(params.CEP) != "" && len(result.Items) == 0 {
			if searchNotice != "" {
				searchNotice += " "
			}
			searchNotice += "Nenhuma empresa encontrada com este CEP na base consultada."
		}
	}
	if cepNotice != "" {
		if searchNotice != "" {
			searchNotice += " "
		}
		searchNotice += cepNotice
	}

	var municipios []api.MunicipioItem
	if params.UF != "" {
		munOut, err := h.leads.AllMunicipios(ctx, params.UF)
		if err == nil {
			municipios = munOut
		}
	}

	title := "Resultados da busca"
	desc := "Listagem de empresas com dados da Receita Federal."
	if params.UF != "" {
		title = fmt.Sprintf("Empresas em %s — LeadB2B", params.UF)
		desc = fmt.Sprintf("Encontre empresas em %s por CNAE, município e situação cadastral.", params.UF)
	}

	sociosCounts := h.sociosCountsForItems(ctx, result.Items)

	pageSize := render.ClampPageSize(params.PageSize)
	pageMeta := render.DefaultMeta(title+" | LeadB2B", desc, r.URL.Path+"?"+r.URL.RawQuery, h.siteURL)
	pageMeta.NoIndex = true
	pageMeta.OGImage = render.OGImageURL(h.siteURL)
	page := BuscaPage{
		SiteData: render.SiteData{
			Meta:        pageMeta,
			HeaderStyle: "header-style-light",
			Skin:        "skin-6",
			HeroTitle:   "Buscar empresas",
			HeroCrumbs: []render.HeroCrumb{
				{Label: "Início", Href: "/"},
				{Label: "Buscar"},
			},
		},
		Items:      result.Items,
		UFs:        h.leads.UFOptions(meta),
		Situacoes:  h.leads.SituacaoOptions(meta),
		Municipios: municipios,
		CNAEs:      nil,
		Params:                  params,
		PorteOptions:            render.PorteOptions(),
		NaturezaJuridicaOptions: render.NaturezaJuridicaOptions(),
		SearchNotice:            searchNotice,
		ActiveFilters: render.BuildActiveFilterTags(params, query, render.FilterTagContext{
			UFs:        h.leads.UFOptions(meta),
			Municipios: municipios,
			Situacoes:  h.leads.SituacaoOptions(meta),
		}),
		HasMore:      result.HasMore,
		SociosCounts: sociosCounts,
	}
	page.Pagination = render.BuildPagination(r.URL, pageNum, pageSize, len(result.Items), result.Total, result.HasMore, derefString(result.NextCursor))
	if result.HasMore && result.NextCursor != nil {
		page.NextPageURL = page.Pagination.NextURL
		page.NextCursor = *result.NextCursor
	}
	_ = h.render.Render(w, "busca", page)
}

func (h *Handler) Empresa(w http.ResponseWriter, r *http.Request) {
	h.empresa(w, r, true)
}

func (h *Handler) EmpresaPremium(w http.ResponseWriter, r *http.Request) {
	h.empresa(w, r, false)
}

func (h *Handler) EmpresaPremiumRedirect(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/login?next=/app/busca", http.StatusFound)
}

func (h *Handler) empresa(w http.ResponseWriter, r *http.Request, locked bool) {
	slug := chi.URLParam(r, "slug")
	cnpj, ok := render.CNPJFromEmpresaSlug(slug)
	if !ok {
		http.Error(w, "CNPJ inválido", http.StatusUnprocessableEntity)
		return
	}

	detail, err := h.leads.GetEstabelecimento(r.Context(), cnpj)
	if err != nil {
		h.renderError(w, err)
		return
	}

	path := render.EmpresaPathFromDetail(detail, !locked)
	canonicalSlug := render.EmpresaSlug(
		cnpj,
		render.EmpresaNomeExibicao(detail),
		detail.Endereco.Municipio.Descricao,
		detail.Endereco.UF,
	)
	if strings.ToLower(slug) != canonicalSlug {
		http.Redirect(w, r, path, http.StatusMovedPermanently)
		return
	}
	nome := render.EmpresaNomeExibicao(detail)
	cnaeLabel := render.CNAEShort(detail.CNAE.Principal.Descricao)
	title := fmt.Sprintf("%s — CNPJ %s, %s | LeadB2B", nome, render.FmtCNPJ(cnpj), cnaeLabel)
	var desc string
	if render.HasText(detail.NomeFantasia) {
		desc = fmt.Sprintf("Ficha de %s (razão social %s) em %s, %s. CNAE: %s. Situação: %s. Dados da Receita Federal.",
			nome, detail.Empresa.RazaoSocial, detail.Endereco.Municipio.Descricao, detail.Endereco.UF, cnaeLabel, detail.SituacaoCadastralDescricao)
	} else {
		desc = fmt.Sprintf("Ficha cadastral de %s em %s, %s. CNAE: %s. Situação: %s. Dados da Receita Federal.",
			nome, detail.Endereco.Municipio.Descricao, detail.Endereco.UF, cnaeLabel, detail.SituacaoCadastralDescricao)
	}

	canonicalPath := render.EmpresaPathFromDetail(detail, false)
	meta := render.DefaultMeta(title, desc, canonicalPath, h.siteURL)
	meta.OGType = "article"
	meta.OGImage = render.OGImageURL(h.siteURL)
	meta.JSONLD = render.EmpresaPageStructuredData(detail, h.siteURL, locked)
	if !locked {
		meta.NoIndex = true
	}

	similar := h.similarCompanies(r.Context(), detail)

	page := EmpresaPage{
		SiteData: render.SiteData{
			Meta:          meta,
			HeaderStyle:   "header-style-light",
			Skin:          "skin-6",
			MinimalAssets: true,
			HeroTitle:     "Ficha da empresa",
			HeroCrumbs: []render.HeroCrumb{
				{Label: "Início", Href: "/"},
				{Label: "Buscar", Href: "/busca"},
				{Label: render.EmpresaNomeExibicao(detail)},
			},
		},
		Detail:       detail,
		Locked:       locked,
		MapCoords:    render.GeocodeEmpresa(detail),
		GeoSummary:   render.EmpresaGeoSummary(detail),
		SimilarItems: similar,
	}
	_ = h.render.Render(w, "empresa", page)
}

func (h *Handler) similarCompanies(ctx context.Context, detail *api.EstabelecimentoDetailOut) []api.EstabelecimentoOut {
	if detail == nil {
		return nil
	}
	cnae := detail.CNAE.Principal.Codigo
	uf := detail.Endereco.UF
	if cnae == "" || uf == "" {
		return nil
	}
	result, err := h.leads.SearchEstabelecimentos(ctx, api.SearchParams{
		UF:                  uf,
		CNAEFiscalPrincipal: []string{cnae},
		SituacaoCadastral:   []string{"2"},
		PageSize:            8,
	})
	if err != nil || result == nil {
		return nil
	}
	out := make([]api.EstabelecimentoOut, 0, 5)
	for _, item := range result.Items {
		if item.CNPJ == detail.CNPJ {
			continue
		}
		out = append(out, item)
		if len(out) >= 5 {
			break
		}
	}
	return out
}

func (h *Handler) Health(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.Write([]byte(`{"status":"ok"}`))
}

func (h *Handler) RedirectLegacy(w http.ResponseWriter, r *http.Request) {
	target := strings.TrimSuffix(r.URL.Path, ".html")
	switch target {
	case "/index":
		target = "/"
	case "/resultados":
		target = "/busca"
	case "/empresa", "/empresa-premium":
		http.Redirect(w, r, "/busca", http.StatusMovedPermanently)
		return
	}
	if r.URL.RawQuery != "" {
		target += "?" + r.URL.RawQuery
	}
	http.Redirect(w, r, target, http.StatusMovedPermanently)
}

func (h *Handler) redirectCNPJLookup(w http.ResponseWriter, r *http.Request, rawCNPJ string) bool {
	digits, ok := render.ExtractCNPJDigits(rawCNPJ)
	if !ok {
		return false
	}
	ctx := r.Context()
	switch len(digits) {
	case 14:
		detail, err := h.leads.GetEstabelecimento(ctx, digits)
		if err != nil {
			return false
		}
		http.Redirect(w, r, render.EmpresaPathFromDetail(detail, false), http.StatusFound)
		return true
	case 8:
		emp, err := h.leads.GetEmpresa(ctx, digits)
		if err != nil || len(emp.Estabelecimentos) == 0 {
			return false
		}
		for _, item := range emp.Estabelecimentos {
			if item.IdentificadorMatrizFilial == 1 {
				http.Redirect(w, r, render.EmpresaPathFromList(item), http.StatusFound)
				return true
			}
		}
		http.Redirect(w, r, render.EmpresaPathFromList(emp.Estabelecimentos[0]), http.StatusFound)
		return true
	default:
		return false
	}
}

func (h *Handler) renderError(w http.ResponseWriter, err error) {
	if apiErr, ok := err.(*api.APIError); ok {
		switch apiErr.Status {
		case http.StatusUnauthorized:
			http.Error(w, "Erro de autenticação com a API. Verifique GLOBALAPI_KEY.", http.StatusBadGateway)
			return
		case http.StatusTooManyRequests:
			http.Error(w, "Muitas requisições. Tente novamente em instantes.", http.StatusTooManyRequests)
			return
		case http.StatusUnprocessableEntity:
			http.Error(w, "Parâmetros inválidos.", http.StatusUnprocessableEntity)
			return
		}
	}
	if isTimeoutError(err) {
		http.Error(w, "A busca demorou mais que o esperado. Tente refinar os filtros e buscar novamente.", http.StatusGatewayTimeout)
		return
	}
	http.Error(w, "Não foi possível carregar os dados. Tente novamente.", http.StatusBadGateway)
}

func isTimeoutError(err error) bool {
	if errors.Is(err, context.DeadlineExceeded) {
		return true
	}
	var netErr net.Error
	return errors.As(err, &netErr) && netErr.Timeout()
}

func derefString(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}
