package api

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
)

type ProductService struct {
	client *Client
}

func NewProductService(client *Client) *ProductService {
	return &ProductService{client: client}
}

func (s *ProductService) withToken(token string) *ProductService {
	return &ProductService{client: s.client.WithToken(token)}
}

func (s *ProductService) SearchCompanies(ctx context.Context, token string, query url.Values) (*MaskedSearchOut, error) {
	var out MaskedSearchOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/search/companies", query, &out)
	return &out, err
}

func (s *ProductService) SearchFilters(ctx context.Context, token string) (*FiltrosMetaOut, error) {
	var out FiltrosMetaOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/search/filters", nil, &out)
	return &out, err
}

func (s *ProductService) SearchNaturezas(ctx context.Context, token string) ([]FiltroOpcao, error) {
	var out FiltroOpcoesOut
	if err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/search/filtros/naturezas-juridicas", nil, &out); err != nil {
		return nil, err
	}
	return out.Items, nil
}

func (s *ProductService) SearchPortes(ctx context.Context, token string) ([]FiltroOpcao, error) {
	var out FiltroOpcoesOut
	if err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/search/filtros/portes", nil, &out); err != nil {
		return nil, err
	}
	return out.Items, nil
}

func (s *ProductService) SearchCNAEs(ctx context.Context, token, q string) ([]CnaeItem, error) {
	query := url.Values{}
	if q != "" {
		query.Set("q", q)
	}
	query.Set("page_size", "200")
	var out CnaesOut
	if err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/search/cnaes", query, &out); err != nil {
		return nil, err
	}
	return out.Items, nil
}

func (s *ProductService) SearchMunicipios(ctx context.Context, token, uf string) ([]MunicipioItem, error) {
	q := url.Values{}
	if uf != "" {
		q.Set("uf", uf)
	}
	var out MunicipiosOut
	if err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/search/municipios", q, &out); err != nil {
		return nil, err
	}
	return out.Items, nil
}

func (s *ProductService) WorkLead(ctx context.Context, token, cnpj string) (*LeadWorkResponse, error) {
	var out LeadWorkResponse
	err := s.withToken(token).client.doJSON(ctx, http.MethodPost, "/api/v1/product/leads/work", LeadWorkRequest{CNPJ: cnpj}, &out)
	return &out, err
}

func (s *ProductService) ListLeads(ctx context.Context, token, stage string) (*LeadListOut, error) {
	q := url.Values{}
	if stage != "" {
		q.Set("stage", stage)
	}
	var out LeadListOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/leads", q, &out)
	return &out, err
}

func (s *ProductService) GetLead(ctx context.Context, token string, leadID int64) (*LeadDetailOut, error) {
	var out LeadDetailOut
	path := fmt.Sprintf("/api/v1/product/leads/%d", leadID)
	err := s.withToken(token).client.do(ctx, http.MethodGet, path, nil, &out)
	return &out, err
}

func (s *ProductService) UpdateLead(ctx context.Context, token string, leadID int64, body any) (*LeadCardOut, error) {
	var out LeadCardOut
	path := fmt.Sprintf("/api/v1/product/leads/%d", leadID)
	err := s.withToken(token).client.doJSON(ctx, http.MethodPatch, path, body, &out)
	return &out, err
}

func (s *ProductService) UpdateLeadNotes(ctx context.Context, token string, leadID int64, notes string) (*LeadCardOut, error) {
	n := notes
	return s.UpdateLead(ctx, token, leadID, LeadUpdateNotes{Notes: &n})
}

func (s *ProductService) GetLeadHistory(ctx context.Context, token string, leadID int64) (*LeadHistoryOut, error) {
	var out LeadHistoryOut
	path := fmt.Sprintf("/api/v1/product/leads/%d/history", leadID)
	err := s.withToken(token).client.do(ctx, http.MethodGet, path, nil, &out)
	return &out, err
}

func (s *ProductService) ListTags(ctx context.Context, token string) ([]TagOut, error) {
	var out []TagOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/tags", nil, &out)
	return out, err
}

func (s *ProductService) CreateTag(ctx context.Context, token string, req TagCreate) (*TagOut, error) {
	var out TagOut
	err := s.withToken(token).client.doJSON(ctx, http.MethodPost, "/api/v1/product/tags", req, &out)
	return &out, err
}

func (s *ProductService) UpdateTag(ctx context.Context, token string, id int64, req TagUpdate) (*TagOut, error) {
	var out TagOut
	path := fmt.Sprintf("/api/v1/product/tags/%d", id)
	err := s.withToken(token).client.doJSON(ctx, http.MethodPatch, path, req, &out)
	return &out, err
}

func (s *ProductService) DeleteTag(ctx context.Context, token string, id int64) error {
	path := fmt.Sprintf("/api/v1/product/tags/%d", id)
	return s.withToken(token).client.do(ctx, http.MethodDelete, path, nil, nil)
}

func (s *ProductService) DeleteLead(ctx context.Context, token string, leadID int64) error {
	path := fmt.Sprintf("/api/v1/product/leads/%d", leadID)
	return s.withToken(token).client.do(ctx, http.MethodDelete, path, nil, nil)
}

func (s *ProductService) UpdateStage(ctx context.Context, token string, leadID int64, stage string) (*LeadCardOut, error) {
	var out LeadCardOut
	path := fmt.Sprintf("/api/v1/product/leads/%d/stage", leadID)
	err := s.withToken(token).client.doJSON(ctx, http.MethodPatch, path, LeadStageUpdate{Stage: stage}, &out)
	return &out, err
}

func (s *ProductService) Kanban(ctx context.Context, token string) (*KanbanBoardOut, error) {
	var out KanbanBoardOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/kanban", nil, &out)
	return &out, err
}

func (s *ProductService) StatsOverview(ctx context.Context, token string) (*StatsOverviewOut, error) {
	var out StatsOverviewOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/stats/overview", nil, &out)
	return &out, err
}

func (s *ProductService) ContactWhatsApp(ctx context.Context, token string, leadID int64, req ContactWhatsAppRequest) (*ContactWhatsAppOut, error) {
	var out ContactWhatsAppOut
	path := fmt.Sprintf("/api/v1/product/leads/%d/contact/whatsapp", leadID)
	err := s.withToken(token).client.doJSON(ctx, http.MethodPost, path, req, &out)
	return &out, err
}

func (s *ProductService) ContactEmail(ctx context.Context, token string, leadID int64, req ContactEmailRequest) (*ContactEmailOut, error) {
	var out ContactEmailOut
	path := fmt.Sprintf("/api/v1/product/leads/%d/contact/email", leadID)
	err := s.withToken(token).client.doJSON(ctx, http.MethodPost, path, req, &out)
	return &out, err
}

func (s *ProductService) ListTemplates(ctx context.Context, token string) ([]TemplateOut, error) {
	var out []TemplateOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/templates", nil, &out)
	return out, err
}

func (s *ProductService) CreateTemplate(ctx context.Context, token string, req TemplateCreate) (*TemplateOut, error) {
	var out TemplateOut
	err := s.withToken(token).client.doJSON(ctx, http.MethodPost, "/api/v1/product/templates", req, &out)
	return &out, err
}

func (s *ProductService) UpdateTemplate(ctx context.Context, token string, id int64, req TemplateUpdate) (*TemplateOut, error) {
	var out TemplateOut
	path := fmt.Sprintf("/api/v1/product/templates/%d", id)
	err := s.withToken(token).client.doJSON(ctx, http.MethodPatch, path, req, &out)
	return &out, err
}

func (s *ProductService) DeleteTemplate(ctx context.Context, token string, id int64) error {
	path := fmt.Sprintf("/api/v1/product/templates/%d", id)
	return s.withToken(token).client.do(ctx, http.MethodDelete, path, nil, nil)
}

func (s *ProductService) ListExports(ctx context.Context, token string) (*ExportListOut, error) {
	var out ExportListOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/exports", nil, &out)
	return &out, err
}

func (s *ProductService) CreateExport(ctx context.Context, token string, leadIDs []int64) (*ExportJobOut, error) {
	var out ExportJobOut
	err := s.withToken(token).client.doJSON(ctx, http.MethodPost, "/api/v1/product/exports", ExportCreateRequest{LeadIDs: leadIDs}, &out)
	return &out, err
}

func (s *ProductService) DownloadExport(ctx context.Context, token string, jobID int64) ([]byte, string, error) {
	path := fmt.Sprintf("/api/v1/product/exports/%d/download", jobID)
	return s.withToken(token).client.doDownload(ctx, http.MethodGet, path, nil)
}

func (s *ProductService) AdminListUsers(ctx context.Context, token string, limit, offset int) (*AdminUserListOut, error) {
	q := url.Values{}
	q.Set("limit", strconv.Itoa(limit))
	q.Set("offset", strconv.Itoa(offset))
	var out AdminUserListOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/admin/users", q, &out)
	return &out, err
}

func (s *ProductService) AdminMetrics(ctx context.Context, token string) (*AdminMetricsOut, error) {
	var out AdminMetricsOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/admin/metrics", nil, &out)
	return &out, err
}

func (s *ProductService) AdminListPlans(ctx context.Context, token string) ([]AdminPlanOut, error) {
	var out []AdminPlanOut
	err := s.withToken(token).client.do(ctx, http.MethodGet, "/api/v1/product/admin/plans", nil, &out)
	return out, err
}

func (s *ProductService) AdminUpdateUserStatus(ctx context.Context, token string, userID int64, status string) error {
	path := fmt.Sprintf("/api/v1/product/admin/users/%d/status", userID)
	return s.withToken(token).client.doJSON(ctx, http.MethodPatch, path, AdminStatusUpdate{Status: status}, nil)
}

func (s *ProductService) AdminUpdateSubscription(ctx context.Context, token string, userID int64, planSlug, status string) error {
	path := fmt.Sprintf("/api/v1/product/admin/users/%d/subscription", userID)
	return s.withToken(token).client.doJSON(ctx, http.MethodPatch, path, AdminSubscriptionUpdate{PlanSlug: planSlug, Status: status}, nil)
}
