package product

import (
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"strings"
	"time"

	"leadb2b.org/internal/api"
	"leadb2b.org/internal/render"
	"leadb2b.org/internal/store"
)

type Service struct {
	db    *store.DB
	leads *api.LeadsService
}

func NewService(db *store.DB, leads *api.LeadsService) *Service {
	return &Service{db: db, leads: leads}
}

func (s *Service) LeadsWithToken(token string) *api.LeadsService {
	return api.NewLeadsService(s.leads.Client().WithToken(token))
}

func (s *Service) WorkLead(ctx context.Context, userID int64, limits *store.UserLimits, sub *store.Subscription, token, cnpj string) (*store.UserLead, *api.EstabelecimentoDetailOut, error) {
	if limits == nil || !limits.CanUseApp {
		return nil, nil, apiErr("PLAN_INACTIVE", "Plano inativo", 403)
	}
	cnpj = render.ExtractCNPJDigitsOnly(cnpj)
	if len(cnpj) != 14 {
		return nil, nil, apiErr("INVALID_CNPJ", "CNPJ inválido", 422)
	}
	if limits.SlotsRemaining <= 0 {
		return nil, nil, apiErr("ACTIVE_LIMIT_REACHED", "Limite de leads ativos atingido. Finalize leads no kanban para liberar espaço.", 409)
	}
	if sub != nil && sub.Plan != nil && sub.Plan.MaxAddPerDay != nil && limits.AddedToday >= *sub.Plan.MaxAddPerDay {
		return nil, nil, apiErr("DAILY_ADD_LIMIT", "Limite diário de novos leads atingido", 429)
	}
	exists, err := s.db.HasLeadCNPJ(ctx, userID, cnpj)
	if err != nil {
		return nil, nil, err
	}
	if exists {
		return nil, nil, apiErr("ALREADY_WORKING", "Você já está trabalhando este lead", 409)
	}
	client := s.leads.Client().WithToken(token)
	detail, err := api.NewLeadsService(client).GetEstabelecimento(ctx, cnpj)
	if err != nil {
		return nil, nil, err
	}
	name := detail.Empresa.RazaoSocial
	if render.HasText(detail.NomeFantasia) {
		name = *detail.NomeFantasia
	}
	lead := &store.UserLead{
		UserID:      userID,
		CNPJ:        cnpj,
		Stage:       store.StageNovos,
		CompanyName: name,
		City:        detail.Endereco.Municipio.Descricao,
		UF:          detail.Endereco.UF,
		HasPhone:    render.HasTelefoneDetail(detail),
		HasEmail:    render.HasEmailDetail(detail),
	}
	if _, err := s.db.CreateUserLead(ctx, lead); err != nil {
		return nil, nil, err
	}
	if err := s.db.IncrementLeadsAdded(ctx, userID); err != nil {
		return nil, nil, err
	}
	created, err := s.db.ListUserLeads(ctx, userID, store.StageNovos)
	if err != nil || len(created) == 0 {
		return lead, detail, nil
	}
	for _, l := range created {
		if l.CNPJ == cnpj {
			return &l, detail, nil
		}
	}
	return lead, detail, nil
}

func (s *Service) KanbanBoard(ctx context.Context, userID int64) (map[string][]store.UserLead, *store.UserLimits, error) {
	leads, err := s.db.ListUserLeads(ctx, userID, "")
	if err != nil {
		return nil, nil, err
	}
	cols := make(map[string][]store.UserLead)
	for _, st := range store.AllStages {
		cols[st] = []store.UserLead{}
	}
	for _, l := range leads {
		cols[l.Stage] = append(cols[l.Stage], l)
	}
	sub, _ := s.db.GetActiveSubscription(ctx, userID)
	limits, err := s.db.ComputeLimits(ctx, userID, sub)
	if err != nil {
		return nil, nil, err
	}
	return cols, limits, nil
}

func (s *Service) MoveStage(ctx context.Context, userID, leadID int64, stage string) error {
	stage = strings.TrimSpace(stage)
	valid := false
	for _, st := range store.AllStages {
		if st == stage {
			valid = true
			break
		}
	}
	if !valid {
		return apiErr("INVALID_STAGE", "Estágio inválido", 422)
	}
	_, err := s.db.GetUserLead(ctx, userID, leadID)
	if err != nil {
		return apiErr("LEAD_NOT_FOUND", "Lead não encontrado", 404)
	}
	return s.db.UpdateLeadStage(ctx, userID, leadID, stage)
}

func (s *Service) StatsOverview(ctx context.Context, userID int64) (map[string]any, error) {
	sub, err := s.db.GetActiveSubscription(ctx, userID)
	if err != nil {
		return nil, err
	}
	limits, err := s.db.ComputeLimits(ctx, userID, sub)
	if err != nil {
		return nil, err
	}
	finished, err := s.db.CountFinishedLeads(ctx, userID)
	if err != nil {
		return nil, err
	}
	contacts, err := s.db.CountContactsToday(ctx, userID)
	if err != nil {
		return nil, err
	}
	rate := 0.0
	total := limits.ActiveLeadsCount + finished
	if total > 0 {
		rate = float64(finished) / float64(total)
	}
	return map[string]any{
		"active_leads":      limits.ActiveLeadsCount,
		"finished_leads":    finished,
		"slots_remaining":   limits.SlotsRemaining,
		"added_today":       limits.AddedToday,
		"contacts_today":    contacts,
		"conversion_rate":   rate,
	}, nil
}

func (s *Service) RenderTemplate(body string, lead *store.UserLead) string {
	out := body
	out = strings.ReplaceAll(out, "{{empresa}}", lead.CompanyName)
	out = strings.ReplaceAll(out, "{{cidade}}", lead.City)
	out = strings.ReplaceAll(out, "{{uf}}", lead.UF)
	return out
}

func (s *Service) WhatsAppContact(ctx context.Context, userID, leadID int64, templateID int64, phoneIndex int, token string) (map[string]string, error) {
	lead, err := s.db.GetUserLead(ctx, userID, leadID)
	if err != nil {
		return nil, apiErr("LEAD_NOT_FOUND", "Lead não encontrado", 404)
	}
	detail, err := s.LeadsWithToken(token).GetEstabelecimento(ctx, lead.CNPJ)
	if err != nil {
		return nil, err
	}
	phones := collectPhones(detail)
	if len(phones) == 0 {
		return nil, apiErr("NO_PHONE", "Nenhum telefone disponível", 422)
	}
	if phoneIndex < 0 || phoneIndex >= len(phones) {
		phoneIndex = 0
	}
	msg := defaultWhatsAppMessage(lead)
	if templateID > 0 {
		tpl, err := s.db.GetMessageTemplate(ctx, userID, templateID)
		if err == nil {
			msg = s.RenderTemplate(tpl.Body, lead)
		}
	}
	phone := phones[phoneIndex]
	waURL := fmt.Sprintf("https://wa.me/%s?text=%s", phone.e164, url.QueryEscape(msg))
	_ = s.db.RecordContactEvent(ctx, leadID, "whatsapp")
	return map[string]string{
		"url":     waURL,
		"message": msg,
		"phone":   phone.display,
	}, nil
}

func (s *Service) EmailContact(ctx context.Context, userID, leadID int64, token string) (map[string]string, error) {
	lead, err := s.db.GetUserLead(ctx, userID, leadID)
	if err != nil {
		return nil, apiErr("LEAD_NOT_FOUND", "Lead não encontrado", 404)
	}
	detail, err := s.LeadsWithToken(token).GetEstabelecimento(ctx, lead.CNPJ)
	if err != nil {
		return nil, err
	}
	email := ""
	if detail.Contatos.Email != nil {
		email = strings.TrimSpace(*detail.Contatos.Email)
	}
	if email == "" {
		return nil, apiErr("NO_EMAIL", "Nenhum e-mail disponível", 422)
	}
	subject := url.QueryEscape("Contato comercial — " + lead.CompanyName)
	mailto := fmt.Sprintf("mailto:%s?subject=%s", email, subject)
	_ = s.db.RecordContactEvent(ctx, leadID, "email")
	return map[string]string{"mailto": mailto, "email": email}, nil
}

type phoneEntry struct {
	e164    string
	display string
}

func collectPhones(d *api.EstabelecimentoDetailOut) []phoneEntry {
	var out []phoneEntry
	add := func(t *api.TelefoneOut) {
		if t == nil || t.DDD == "" || t.Numero == "" {
			return
		}
		digits := strings.Map(func(r rune) rune {
			if r >= '0' && r <= '9' {
				return r
			}
			return -1
		}, t.DDD+t.Numero)
		if digits == "" {
			return
		}
		out = append(out, phoneEntry{
			e164:    "55" + digits,
			display: t.Completo,
		})
	}
	add(d.Contatos.Telefone1)
	add(d.Contatos.Telefone2)
	return out
}

func defaultWhatsAppMessage(lead *store.UserLead) string {
	return fmt.Sprintf("Olá %s, tudo bem? Gostaria de conversar sobre uma oportunidade de parceria.", lead.CompanyName)
}

func (s *Service) CreateExport(ctx context.Context, userID int64, limits *store.UserLimits, leadIDs []int64) (int64, []store.UserLead, error) {
	if limits == nil || !limits.CanExport {
		return 0, nil, apiErr("FEATURE_DISABLED", "Exportação não disponível no seu plano", 403)
	}
	if limits.ExportsToday >= limits.MaxExportPerDay {
		return 0, nil, apiErr("EXPORT_LIMIT", "Limite diário de exportações atingido", 429)
	}
	if len(leadIDs) == 0 || len(leadIDs) > 100 {
		return 0, nil, apiErr("INVALID_REQUEST", "Selecione entre 1 e 100 leads", 422)
	}
	var rows []store.UserLead
	for _, id := range leadIDs {
		lead, err := s.db.GetUserLead(ctx, userID, id)
		if err != nil {
			continue
		}
		if lead.Stage == store.StageNovos {
			continue
		}
		hasContact, _ := s.db.HasContactEvent(ctx, id)
		if !hasContact {
			continue
		}
		rows = append(rows, *lead)
	}
	if len(rows) == 0 {
		return 0, nil, apiErr("INVALID_REQUEST", "Nenhum lead elegível (exige contato registrado e stage ≠ novos)", 422)
	}
	idsJSON, _ := json.Marshal(leadIDs)
	jobID, err := s.db.CreateExportJob(ctx, userID, string(idsJSON), len(rows))
	if err != nil {
		return 0, nil, err
	}
	if err := s.db.IncrementExports(ctx, userID); err != nil {
		return 0, nil, err
	}
	return jobID, rows, nil
}

func (s *Service) ExportCSV(rows []store.UserLead) string {
	var b strings.Builder
	b.WriteString("cnpj,empresa,cidade,uf,stage,trabalhado_em\n")
	for _, r := range rows {
		b.WriteString(fmt.Sprintf("%s,%q,%q,%s,%s,%s\n",
			r.CNPJ, r.CompanyName, r.City, r.UF, r.Stage, r.WorkedAt.Format(time.RFC3339)))
	}
	return b.String()
}
