package api

import (
	"context"
	"net/url"
	"strings"
	"sync"
	"time"
)

type SearchParams struct {
	UF                        string
	Municipio                 string
	CNAEFiscalPrincipal       []string
	SituacaoCadastral         []string
	IdentificadorMatrizFilial string
	TemTelefone               string
	TemEmail                  string
	TemSocios                 string
	TemWhatsApp               string
	CNAESecundario            []string
	Q                         string
	CNPJ                      string // campo do formulário; enviado à API via q=
	CEP                       string
	Bairro                    string
	Logradouro                string
	MEI                       string
	Simples                   string
	Porte                     string
	NaturezaJuridica          string
	DataInicioAno             string
	DataInicioMes             string
	PageSize                  int
	Cursor                    string
}

func (p SearchParams) ToQuery() url.Values {
	q := url.Values{}
	if p.UF != "" {
		q.Set("uf", p.UF)
	}
	if p.Municipio != "" {
		q.Set("municipio", p.Municipio)
	}
	for _, c := range p.CNAEFiscalPrincipal {
		if c != "" {
			q.Add("cnae_fiscal_principal", c)
		}
	}
	for _, c := range p.CNAESecundario {
		if c != "" {
			q.Add("cnae_secundario", c)
		}
	}
	for _, s := range p.SituacaoCadastral {
		if s != "" {
			q.Add("situacao_cadastral", s)
		}
	}
	if p.IdentificadorMatrizFilial != "" {
		q.Set("identificador_matriz_filial", p.IdentificadorMatrizFilial)
	}
	if v := effectiveSearchQ(p.Q, p.CNPJ); v != "" {
		q.Set("q", v)
	}
	setOptionalBoolParam(q, "tem_telefone", p.TemTelefone)
	setOptionalBoolParam(q, "tem_email", p.TemEmail)
	setOptionalBoolParam(q, "tem_socios", p.TemSocios)
	setOptionalBoolParam(q, "tem_whatsapp", p.TemWhatsApp)
	setOptionalBoolParam(q, "mei", p.MEI)
	setOptionalBoolParam(q, "simples", p.Simples)
	if cep := normalizeCEPDigits(p.CEP); cep != "" {
		q.Set("cep", cep)
	}
	if p.Bairro != "" {
		q.Set("bairro", p.Bairro)
	}
	if p.Logradouro != "" {
		q.Set("logradouro", p.Logradouro)
	}
	if p.Porte != "" {
		q.Set("porte", p.Porte)
	}
	if p.NaturezaJuridica != "" {
		q.Set("natureza_juridica", p.NaturezaJuridica)
	}
	if p.DataInicioAno != "" {
		q.Set("data_inicio_ano", p.DataInicioAno)
	}
	if p.DataInicioMes != "" {
		q.Set("data_inicio_mes", p.DataInicioMes)
	}
	if p.PageSize > 0 {
		q.Set("page_size", itoa(p.PageSize))
	}
	if p.Cursor != "" {
		q.Set("cursor", p.Cursor)
	}
	return q
}

func normalizeCEPDigits(raw string) string {
	digits := strings.Map(func(r rune) rune {
		if r >= '0' && r <= '9' {
			return r
		}
		return -1
	}, raw)
	if len(digits) != 8 {
		return ""
	}
	return digits
}

func effectiveSearchQ(q, cnpj string) string {
	q = strings.TrimSpace(q)
	cnpj = strings.TrimSpace(cnpj)
	if cnpj != "" && q == "" {
		return cnpj
	}
	return q
}

func setOptionalBoolParam(q url.Values, key, value string) {
	if value == "true" || value == "false" {
		q.Set(key, value)
	}
}

func itoa(n int) string {
	if n == 0 {
		return "0"
	}
	var b [12]byte
	i := len(b)
	for n > 0 {
		i--
		b[i] = byte('0' + n%10)
		n /= 10
	}
	return string(b[i:])
}

type filtrosCache struct {
	mu      sync.RWMutex
	data    *FiltrosMetaOut
	expires time.Time
}

type cnaesCache struct {
	mu      sync.RWMutex
	items   []CnaeItem
	expires time.Time
}

type municipiosCache struct {
	mu    sync.RWMutex
	byUF  map[string]municipiosCacheEntry
}

type municipiosCacheEntry struct {
	items   []MunicipioItem
	expires time.Time
}

type sociosCountCache struct {
	mu       sync.RWMutex
	byBasico map[string]sociosCountCacheEntry
}

type sociosCountCacheEntry struct {
	count   int
	expires time.Time
}

type LeadsService struct {
	client      *Client
	filtros     filtrosCache
	cnaes       cnaesCache
	municipios  municipiosCache
	sociosCount sociosCountCache
	ttl         time.Duration
}

func (s *LeadsService) Client() *Client {
	return s.client
}

func NewLeadsService(client *Client) *LeadsService {
	return &LeadsService{
		client: client,
		municipios: municipiosCache{
			byUF: make(map[string]municipiosCacheEntry),
		},
		sociosCount: sociosCountCache{
			byBasico: make(map[string]sociosCountCacheEntry),
		},
		ttl: 15 * time.Minute,
	}
}

func (s *LeadsService) Filtros(ctx context.Context) (*FiltrosMetaOut, error) {
	s.filtros.mu.RLock()
	if s.filtros.data != nil && time.Now().Before(s.filtros.expires) {
		defer s.filtros.mu.RUnlock()
		return s.filtros.data, nil
	}
	s.filtros.mu.RUnlock()

	var out FiltrosMetaOut
	if err := s.client.do(ctx, "GET", "/api/v1/leads/filtros", nil, &out); err != nil {
		return nil, err
	}

	s.filtros.mu.Lock()
	s.filtros.data = &out
	s.filtros.expires = time.Now().Add(s.ttl)
	s.filtros.mu.Unlock()
	return &out, nil
}

func (s *LeadsService) Municipios(ctx context.Context, uf, search, cursor string, pageSize int) (*MunicipiosOut, error) {
	if uf == "" {
		return nil, &APIError{Status: 422, Message: "uf é obrigatório"}
	}
	if search == "" && cursor == "" {
		s.municipios.mu.RLock()
		if entry, ok := s.municipios.byUF[uf]; ok && time.Now().Before(entry.expires) {
			defer s.municipios.mu.RUnlock()
			return &MunicipiosOut{
				Param: "municipio",
				Total: len(entry.items),
				Items: entry.items,
			}, nil
		}
		s.municipios.mu.RUnlock()
	}

	q := url.Values{"uf": {uf}}
	if search != "" {
		q.Set("q", search)
		if pageSize > 0 {
			q.Set("page_size", itoa(pageSize))
		}
		if cursor != "" {
			q.Set("cursor", cursor)
		}
	}
	var out MunicipiosOut
	if err := s.client.do(ctx, "GET", "/api/v1/leads/filtros/municipios", q, &out); err != nil {
		return nil, err
	}

	if search == "" && cursor == "" && len(out.Items) > 0 {
		items := append([]MunicipioItem(nil), out.Items...)
		s.municipios.mu.Lock()
		s.municipios.byUF[uf] = municipiosCacheEntry{
			items:   items,
			expires: time.Now().Add(s.ttl),
		}
		s.municipios.mu.Unlock()
	}
	return &out, nil
}

func (s *LeadsService) CNAEs(ctx context.Context, search, cursor string, pageSize int) (*CnaesOut, error) {
	if search == "" && cursor == "" {
		s.cnaes.mu.RLock()
		if len(s.cnaes.items) > 0 && time.Now().Before(s.cnaes.expires) {
			defer s.cnaes.mu.RUnlock()
			return &CnaesOut{
				Param: "cnae_fiscal_principal",
				Total: len(s.cnaes.items),
				Items: s.cnaes.items,
			}, nil
		}
		s.cnaes.mu.RUnlock()
	}

	q := url.Values{}
	if search != "" {
		q.Set("q", search)
		if pageSize > 0 {
			q.Set("page_size", itoa(pageSize))
		}
		if cursor != "" {
			q.Set("cursor", cursor)
		}
	}
	var out CnaesOut
	if err := s.client.do(ctx, "GET", "/api/v1/leads/filtros/cnaes", q, &out); err != nil {
		return nil, err
	}

	if search == "" && cursor == "" && len(out.Items) > 0 {
		items := append([]CnaeItem(nil), out.Items...)
		s.cnaes.mu.Lock()
		s.cnaes.items = items
		s.cnaes.expires = time.Now().Add(s.ttl)
		s.cnaes.mu.Unlock()
	}
	return &out, nil
}

func (s *LeadsService) AllMunicipios(ctx context.Context, uf string) ([]MunicipioItem, error) {
	out, err := s.Municipios(ctx, uf, "", "", 0)
	if err != nil {
		return nil, err
	}
	return out.Items, nil
}

func (s *LeadsService) AllCNAEs(ctx context.Context) ([]CnaeItem, error) {
	out, err := s.CNAEs(ctx, "", "", 0)
	if err != nil {
		return nil, err
	}
	return out.Items, nil
}

func (s *LeadsService) SearchEstabelecimentos(ctx context.Context, params SearchParams) (*EstabelecimentoSearchOut, error) {
	return s.SearchEstabelecimentosQuery(ctx, params.ToQuery())
}

func (s *LeadsService) SearchEstabelecimentosQuery(ctx context.Context, query url.Values) (*EstabelecimentoSearchOut, error) {
	var out EstabelecimentoSearchOut
	if err := s.client.do(ctx, "GET", "/api/v1/leads/estabelecimentos", query, &out); err != nil {
		return nil, err
	}
	return &out, nil
}

func (s *LeadsService) GetEstabelecimento(ctx context.Context, cnpj14 string) (*EstabelecimentoDetailOut, error) {
	var out EstabelecimentoDetailOut
	path := "/api/v1/leads/estabelecimentos/" + url.PathEscape(cnpj14)
	if err := s.client.do(ctx, "GET", path, nil, &out); err != nil {
		return nil, err
	}
	return &out, nil
}

func (s *LeadsService) GetEmpresa(ctx context.Context, cnpjBasico string) (*EmpresaDetailOut, error) {
	var out EmpresaDetailOut
	path := "/api/v1/leads/empresas/" + url.PathEscape(cnpjBasico)
	if err := s.client.do(ctx, "GET", path, nil, &out); err != nil {
		return nil, err
	}
	return &out, nil
}

func countSociosCadastrados(socios []SocioOut) int {
	n := 0
	for _, s := range socios {
		name := strings.TrimSpace(s.NomeSocioRazaoSocial)
		if name == "" {
			name = strings.TrimSpace(s.NomeSocio)
		}
		if name != "" {
			n++
		}
	}
	return n
}

// SociosCount retorna quantos sócios a empresa possui (cache 15 min por CNPJ básico).
func (s *LeadsService) SociosCount(ctx context.Context, cnpjBasico string) (int, error) {
	cnpjBasico = strings.TrimSpace(cnpjBasico)
	if cnpjBasico == "" {
		return 0, nil
	}

	s.sociosCount.mu.RLock()
	if entry, ok := s.sociosCount.byBasico[cnpjBasico]; ok && time.Now().Before(entry.expires) {
		defer s.sociosCount.mu.RUnlock()
		return entry.count, nil
	}
	s.sociosCount.mu.RUnlock()

	detail, err := s.GetEmpresa(ctx, cnpjBasico)
	if err != nil {
		return 0, err
	}
	count := countSociosCadastrados(detail.Socios)

	s.sociosCount.mu.Lock()
	s.sociosCount.byBasico[cnpjBasico] = sociosCountCacheEntry{
		count:   count,
		expires: time.Now().Add(s.ttl),
	}
	s.sociosCount.mu.Unlock()
	return count, nil
}

func (s *LeadsService) UFOptions(meta *FiltrosMetaOut) []FiltroOpcao {
	for _, f := range meta.Filtros {
		if f.Param == "uf" {
			return f.Opcoes
		}
	}
	return nil
}

func (s *LeadsService) SituacaoOptions(meta *FiltrosMetaOut) []FiltroOpcao {
	for _, f := range meta.Filtros {
		if f.Param == "situacao_cadastral" {
			return f.Opcoes
		}
	}
	return nil
}
