package usecase

import (
	"context"
	"strings"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	libkg "haixun-backend/internal/library/knowledge"
	"haixun-backend/internal/library/placement"
	"haixun-backend/internal/model/brand/domain/entity"
	domrepo "haixun-backend/internal/model/brand/domain/repository"
	domusecase "haixun-backend/internal/model/brand/domain/usecase"

	"github.com/google/uuid"
)

type brandUseCase struct {
	repo domrepo.Repository
}

func NewUseCase(repo domrepo.Repository) domusecase.UseCase {
	return &brandUseCase{repo: repo}
}

func (u *brandUseCase) List(ctx context.Context, tenantID, ownerUID string) (*domusecase.ListResult, error) {
	if err := requireActor(tenantID, ownerUID); err != nil {
		return nil, err
	}
	items, err := u.repo.ListByOwner(ctx, tenantID, ownerUID)
	if err != nil {
		return nil, err
	}
	list := make([]domusecase.BrandSummary, 0, len(items))
	for _, item := range items {
		list = append(list, toSummary(item))
	}
	return &domusecase.ListResult{List: list}, nil
}

func (u *brandUseCase) Create(ctx context.Context, req domusecase.CreateRequest) (*domusecase.BrandSummary, error) {
	if err := requireActor(req.TenantID, req.OwnerUID); err != nil {
		return nil, err
	}
	displayName := strings.TrimSpace(req.DisplayName)
	if displayName == "" {
		existing, err := u.repo.ListByOwner(ctx, req.TenantID, req.OwnerUID)
		if err != nil {
			return nil, err
		}
		displayName = "品牌 " + itoa(len(existing)+1)
	}
	productBrief := strings.TrimSpace(req.ProductBrief)
	if productBrief == "" && strings.TrimSpace(req.ProductContext) != "" {
		productBrief = strings.TrimSpace(req.ProductContext)
	}
	brand := &entity.Brand{
		ID:             uuid.NewString(),
		TenantID:       req.TenantID,
		OwnerUID:       req.OwnerUID,
		DisplayName:    displayName,
		SeedQuery:      strings.TrimSpace(req.SeedQuery),
		Brief:          strings.TrimSpace(req.Brief),
		ProductBrief:   productBrief,
		ProductContext: strings.TrimSpace(req.ProductContext),
		TargetAudience: strings.TrimSpace(req.TargetAudience),
		Goals:          strings.TrimSpace(req.Goals),
		Status:         entity.StatusOpen,
	}
	if req.ResearchMap != nil {
		brand.ResearchMap = *req.ResearchMap
	}
	item, err := u.repo.Create(ctx, brand)
	if err != nil {
		return nil, err
	}
	summary := toSummary(item)
	return &summary, nil
}

func (u *brandUseCase) Get(ctx context.Context, tenantID, ownerUID, brandID string) (*domusecase.BrandSummary, error) {
	item, err := u.assertOwned(ctx, tenantID, ownerUID, brandID)
	if err != nil {
		return nil, err
	}
	summary := toSummary(item)
	return &summary, nil
}

func (u *brandUseCase) Delete(ctx context.Context, tenantID, ownerUID, brandID string) error {
	if _, err := u.assertOwned(ctx, tenantID, ownerUID, brandID); err != nil {
		return err
	}
	return u.repo.SoftDelete(ctx, tenantID, ownerUID, brandID)
}

func (u *brandUseCase) Update(ctx context.Context, req domusecase.UpdateRequest) (*domusecase.BrandSummary, error) {
	brand, err := u.assertOwned(ctx, req.TenantID, req.OwnerUID, req.BrandID)
	if err != nil {
		return nil, err
	}
	patch := patchToMap(req.Patch)
	if req.Patch.ProductID != nil {
		snapshot := placement.ResolveBrandProductContext(*brand, strings.TrimSpace(*req.Patch.ProductID), "")
		patch["product_context"] = snapshot
	}
	item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.BrandID, patch)
	if err != nil {
		return nil, err
	}
	summary := toSummary(item)
	return &summary, nil
}

func (u *brandUseCase) assertOwned(ctx context.Context, tenantID, ownerUID, brandID string) (*entity.Brand, error) {
	if err := requireActor(tenantID, ownerUID); err != nil {
		return nil, err
	}
	if strings.TrimSpace(brandID) == "" {
		return nil, app.For(code.Brand).InputMissingRequired("brand id is required")
	}
	return u.repo.FindByID(ctx, tenantID, ownerUID, brandID)
}

func requireActor(tenantID, ownerUID string) error {
	if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
		return app.For(code.Brand).InputMissingRequired("tenant_id and uid are required")
	}
	return nil
}

func toSummary(item *entity.Brand) domusecase.BrandSummary {
	if item == nil {
		return domusecase.BrandSummary{}
	}
	products := make([]domusecase.ProductSummary, 0, len(item.Products))
	for _, product := range item.Products {
		products = append(products, toProductSummary(product))
	}
	return domusecase.BrandSummary{
		ID:             item.ID,
		DisplayName:    item.DisplayName,
		TopicName:      item.TopicName,
		SeedQuery:      item.SeedQuery,
		Brief:          item.Brief,
		ProductBrief:   item.ProductBrief,
		ProductContext: item.ProductContext,
		ProductID:      item.ProductID,
		Products:       products,
		TargetAudience: item.TargetAudience,
		Goals:          item.Goals,
		ResearchMap:    item.ResearchMap,
		CreateAt:       item.CreateAt,
		UpdateAt:       item.UpdateAt,
	}
}

func patchToMap(patch domusecase.BrandPatch) map[string]interface{} {
	out := map[string]interface{}{}
	if patch.DisplayName != nil {
		out["display_name"] = strings.TrimSpace(*patch.DisplayName)
	}
	if patch.TopicName != nil {
		out["topic_name"] = strings.TrimSpace(*patch.TopicName)
	}
	if patch.SeedQuery != nil {
		out["seed_query"] = strings.TrimSpace(*patch.SeedQuery)
	}
	if patch.Brief != nil {
		out["brief"] = strings.TrimSpace(*patch.Brief)
	}
	if patch.ProductBrief != nil {
		out["product_brief"] = strings.TrimSpace(*patch.ProductBrief)
	}
	if patch.ProductContext != nil {
		out["product_context"] = strings.TrimSpace(*patch.ProductContext)
	}
	if patch.ProductID != nil {
		out["product_id"] = strings.TrimSpace(*patch.ProductID)
	}
	if patch.TargetAudience != nil {
		out["target_audience"] = strings.TrimSpace(*patch.TargetAudience)
	}
	if patch.Goals != nil {
		out["goals"] = strings.TrimSpace(*patch.Goals)
	}
	if patch.AudienceSummary != nil {
		out["research_map.audience_summary"] = strings.TrimSpace(*patch.AudienceSummary)
	}
	if patch.ContentGoal != nil {
		out["research_map.content_goal"] = strings.TrimSpace(*patch.ContentGoal)
	}
	if patch.QuestionsSet {
		out["research_map.questions"] = cleanStringList(patch.Questions)
	}
	if patch.PillarsSet {
		out["research_map.pillars"] = cleanStringList(patch.Pillars)
	}
	if patch.ExclusionsSet {
		out["research_map.exclusions"] = cleanStringList(patch.Exclusions)
	}
	if patch.ResearchMap != nil {
		out["research_map"] = *patch.ResearchMap
	}
	if patch.PatrolKeywordsSet {
		out["research_map.patrol_keywords"] = libkg.NormalizePatrolKeywordList(patch.PatrolKeywords)
	}
	return out
}

func cleanStringList(items []string) []string {
	out := make([]string, 0, len(items))
	seen := make(map[string]struct{}, len(items))
	for _, item := range items {
		trimmed := strings.TrimSpace(item)
		if trimmed == "" {
			continue
		}
		if _, ok := seen[trimmed]; ok {
			continue
		}
		seen[trimmed] = struct{}{}
		out = append(out, trimmed)
	}
	return out
}

func itoa(n int) string {
	if n <= 0 {
		return "1"
	}
	buf := make([]byte, 0, 12)
	for n > 0 {
		buf = append(buf, byte('0'+n%10))
		n /= 10
	}
	for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
		buf[i], buf[j] = buf[j], buf[i]
	}
	return string(buf)
}
