package usecase

import (
	"context"
	"reflect"
	"strings"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	libkg "haixun-backend/internal/library/knowledge"
	brandentity "haixun-backend/internal/model/brand/domain/entity"
	brandrepo "haixun-backend/internal/model/brand/domain/repository"
	kgdomusecase "haixun-backend/internal/model/knowledge_graph/domain/usecase"
	"haixun-backend/internal/model/placement_topic/domain/entity"
	domrepo "haixun-backend/internal/model/placement_topic/domain/repository"
	domusecase "haixun-backend/internal/model/placement_topic/domain/usecase"
	scanpostrepo "haixun-backend/internal/model/scan_post/domain/repository"

	"github.com/google/uuid"
)

type topicUseCase struct {
	repo      domrepo.Repository
	brandRepo brandrepo.Repository
	kg        kgdomusecase.UseCase
	scanRepo  scanpostrepo.Repository
}

func NewUseCase(
	repo domrepo.Repository,
	brandRepo brandrepo.Repository,
	kg kgdomusecase.UseCase,
	scanRepo scanpostrepo.Repository,
) domusecase.UseCase {
	return &topicUseCase{repo: repo, brandRepo: brandRepo, kg: kg, scanRepo: scanRepo}
}

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

func (u *topicUseCase) Create(ctx context.Context, req domusecase.CreateRequest) (*domusecase.TopicSummary, error) {
	if err := requireActor(req.TenantID, req.OwnerUID); err != nil {
		return nil, err
	}
	brandID := strings.TrimSpace(req.BrandID)
	if brandID == "" {
		return nil, app.For(code.Brand).InputMissingRequired("brand_id is required")
	}
	topicName := strings.TrimSpace(req.TopicName)
	seedQuery := strings.TrimSpace(req.SeedQuery)
	brief := strings.TrimSpace(req.Brief)
	if topicName == "" {
		return nil, app.For(code.Brand).InputMissingRequired("topic_name is required")
	}
	if seedQuery == "" || brief == "" {
		return nil, app.For(code.Brand).InputMissingRequired("seed_query and brief are required")
	}
	brand, err := u.brandRepo.FindByID(ctx, req.TenantID, req.OwnerUID, brandID)
	if err != nil {
		return nil, err
	}
	productID := strings.TrimSpace(req.ProductID)
	if productID != "" && !brandHasProduct(brand, productID) {
		return nil, app.For(code.Brand).InputMissingRequired("product not found on brand")
	}
	topic := &entity.Topic{
		ID:        uuid.NewString(),
		TenantID:  req.TenantID,
		OwnerUID:  req.OwnerUID,
		BrandID:   brandID,
		TopicName: topicName,
		SeedQuery: seedQuery,
		Brief:     brief,
		ProductID: productID,
		Status:    entity.StatusOpen,
	}
	item, err := u.repo.Create(ctx, topic)
	if err != nil {
		return nil, err
	}
	summary := toSummary(item, brand.DisplayName)
	return &summary, nil
}

func (u *topicUseCase) Get(ctx context.Context, tenantID, ownerUID, topicID string) (*domusecase.TopicSummary, error) {
	if err := u.migrateLegacyBrands(ctx, tenantID, ownerUID); err != nil {
		return nil, err
	}
	item, err := u.assertOwned(ctx, tenantID, ownerUID, topicID)
	if err != nil {
		return nil, err
	}
	displayName := ""
	var brand *brandentity.Brand
	if loaded, err := u.brandRepo.FindByID(ctx, tenantID, ownerUID, item.BrandID); err == nil && loaded != nil {
		brand = loaded
		displayName = brand.DisplayName
	}
	summary := toSummary(item, displayName)
	if summary.ResearchMap.IsEmpty() && brand != nil && !brand.ResearchMap.IsEmpty() {
		summary.ResearchMap = brand.ResearchMap
	}
	return &summary, nil
}

func (u *topicUseCase) Update(ctx context.Context, req domusecase.UpdateRequest) (*domusecase.TopicSummary, error) {
	topic, err := u.assertOwned(ctx, req.TenantID, req.OwnerUID, req.TopicID)
	if err != nil {
		return nil, err
	}
	if req.Patch.BrandID != nil {
		brandID := strings.TrimSpace(*req.Patch.BrandID)
		if brandID == "" {
			return nil, app.For(code.Brand).InputMissingRequired("brand_id is required")
		}
		if _, err := u.brandRepo.FindByID(ctx, req.TenantID, req.OwnerUID, brandID); err != nil {
			return nil, err
		}
	}
	if req.Patch.ProductID != nil {
		productID := strings.TrimSpace(*req.Patch.ProductID)
		if productID != "" {
			brandID := topic.BrandID
			if req.Patch.BrandID != nil {
				brandID = strings.TrimSpace(*req.Patch.BrandID)
			}
			brand, err := u.brandRepo.FindByID(ctx, req.TenantID, req.OwnerUID, brandID)
			if err != nil {
				return nil, err
			}
			if !brandHasProduct(brand, productID) {
				return nil, app.For(code.Brand).InputMissingRequired("product not found on brand")
			}
		}
	}
	patch := patchToMap(req.Patch)
	item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.TopicID, patch)
	if err != nil {
		return nil, err
	}
	displayName := ""
	if brand, err := u.brandRepo.FindByID(ctx, req.TenantID, req.OwnerUID, item.BrandID); err == nil && brand != nil {
		displayName = brand.DisplayName
	}
	summary := toSummary(item, displayName)
	return &summary, nil
}

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

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

func (u *topicUseCase) migrateLegacyBrands(ctx context.Context, tenantID, ownerUID string) error {
	if u.brandRepo == nil {
		return nil
	}
	brands, err := u.brandRepo.ListByOwner(ctx, tenantID, ownerUID)
	if err != nil {
		return err
	}
	for _, brand := range brands {
		if !legacyBrandHasTopicSignals(brand) {
			continue
		}
		existing, err := u.repo.ListByBrand(ctx, tenantID, ownerUID, brand.ID)
		if err != nil {
			return err
		}
		if legacyTopicAlreadyMigrated(brand, existing) {
			continue
		}
		topic := &entity.Topic{
			ID:          uuid.NewString(),
			TenantID:    tenantID,
			OwnerUID:    ownerUID,
			BrandID:     brand.ID,
			TopicName:   legacyTopicName(brand),
			SeedQuery:   strings.TrimSpace(brand.SeedQuery),
			Brief:       strings.TrimSpace(brand.Brief),
			ProductID:   strings.TrimSpace(brand.ProductID),
			ResearchMap: brand.ResearchMap,
			Status:      entity.StatusOpen,
		}
		created, err := u.repo.Create(ctx, topic)
		if err != nil {
			return err
		}
		if u.kg != nil {
			_ = u.kg.AttachTopicID(ctx, tenantID, ownerUID, brand.ID, created.ID)
		}
		if u.scanRepo != nil {
			_ = u.scanRepo.AttachTopicID(ctx, tenantID, ownerUID, brand.ID, created.ID)
		}
		_, _ = u.brandRepo.Update(ctx, tenantID, ownerUID, brand.ID, map[string]interface{}{
			"topic_name":   "",
			"seed_query":   "",
			"brief":        "",
			"product_id":   "",
			"research_map": brandentity.ResearchMap{},
		})
	}
	return nil
}

func (u *topicUseCase) brandDisplayNames(ctx context.Context, tenantID, ownerUID string) (map[string]string, error) {
	brands, err := u.brandRepo.ListByOwner(ctx, tenantID, ownerUID)
	if err != nil {
		return nil, err
	}
	out := make(map[string]string, len(brands))
	for _, brand := range brands {
		out[brand.ID] = brand.DisplayName
	}
	return out, nil
}

func legacyBrandHasTopicSignals(brand *brandentity.Brand) bool {
	if brand == nil {
		return false
	}
	return strings.TrimSpace(brand.TopicName) != "" ||
		strings.TrimSpace(brand.SeedQuery) != "" ||
		strings.TrimSpace(brand.Brief) != "" ||
		!brand.ResearchMap.IsEmpty()
}

func legacyTopicAlreadyMigrated(brand *brandentity.Brand, existing []*entity.Topic) bool {
	if brand == nil {
		return true
	}
	legacyName := strings.TrimSpace(brand.TopicName)
	legacySeed := strings.TrimSpace(brand.SeedQuery)
	legacyBrief := strings.TrimSpace(brand.Brief)
	legacyProductID := strings.TrimSpace(brand.ProductID)
	for _, item := range existing {
		if item == nil {
			continue
		}
		sameFields := strings.TrimSpace(item.TopicName) == legacyName &&
			strings.TrimSpace(item.SeedQuery) == legacySeed &&
			strings.TrimSpace(item.Brief) == legacyBrief &&
			strings.TrimSpace(item.ProductID) == legacyProductID
		if sameFields {
			return true
		}
		if legacyName == "" && legacySeed == "" && legacyBrief == "" &&
			!brand.ResearchMap.IsEmpty() &&
			reflect.DeepEqual(item.ResearchMap, brand.ResearchMap) {
			return true
		}
	}
	return false
}

func legacyTopicName(brand *brandentity.Brand) string {
	if brand == nil {
		return ""
	}
	if name := strings.TrimSpace(brand.TopicName); name != "" {
		return name
	}
	for _, pillar := range brand.ResearchMap.Pillars {
		if name := strings.TrimSpace(pillar); name != "" {
			return name
		}
	}
	for _, question := range brand.ResearchMap.Questions {
		if name := strings.TrimSpace(question); name != "" {
			return name
		}
	}
	return strings.TrimSpace(brand.DisplayName)
}

func brandHasProduct(brand *brandentity.Brand, productID string) bool {
	if brand == nil {
		return false
	}
	for _, product := range brand.Products {
		if product.ID == productID {
			return true
		}
	}
	return false
}

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.Topic, brandDisplayName string) domusecase.TopicSummary {
	if item == nil {
		return domusecase.TopicSummary{}
	}
	return domusecase.TopicSummary{
		ID:               item.ID,
		BrandID:          item.BrandID,
		BrandDisplayName: brandDisplayName,
		TopicName:        item.TopicName,
		SeedQuery:        item.SeedQuery,
		Brief:            item.Brief,
		ProductID:        item.ProductID,
		ResearchMap:      item.ResearchMap,
		CreateAt:         item.CreateAt,
		UpdateAt:         item.UpdateAt,
	}
}

func patchToMap(patch domusecase.TopicPatch) map[string]interface{} {
	out := map[string]interface{}{}
	if patch.BrandID != nil {
		out["brand_id"] = strings.TrimSpace(*patch.BrandID)
	}
	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.ProductID != nil {
		out["product_id"] = strings.TrimSpace(*patch.ProductID)
	}
	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
}
