package usecase

import (
	"context"
	"strings"

	"haixun-backend/internal/library/clock"
	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	libmongo "haixun-backend/internal/library/mongo"
	"haixun-backend/internal/library/placement"
	"haixun-backend/internal/model/scan_post/domain/entity"
	domrepo "haixun-backend/internal/model/scan_post/domain/repository"
	domusecase "haixun-backend/internal/model/scan_post/domain/usecase"

	"github.com/google/uuid"
)

type scanPostUseCase struct {
	repo domrepo.Repository
}

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

func (u *scanPostUseCase) ClearCopyMissionViralScan(ctx context.Context, tenantID, ownerUID, personaID, copyMissionID string) error {
	if err := requireViralActor(tenantID, ownerUID, personaID); err != nil {
		return err
	}
	copyMissionID = strings.TrimSpace(copyMissionID)
	if copyMissionID == "" {
		return app.For(code.Persona).InputMissingRequired("copy_mission_id is required")
	}
	return u.repo.ReplaceForViralScan(ctx, tenantID, ownerUID, personaID, copyMissionID, "", nil)
}

func (u *scanPostUseCase) ReplaceFromViralScan(ctx context.Context, req domusecase.ViralReplaceRequest) (int, error) {
	if err := requireViralActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return 0, err
	}
	now := clock.NowUnixNano()
	entities := make([]entity.ScanPost, 0, len(req.Posts))
	for _, item := range req.Posts {
		entities = append(entities, entity.ScanPost{
			ID:              uuid.NewString(),
			TenantID:        req.TenantID,
			OwnerUID:        req.OwnerUID,
			LegacyPersonaID: req.PersonaID,
			CopyMissionID:   strings.TrimSpace(req.CopyMissionID),
			Flow:            entity.FlowViral,
			ScanJobID:       req.ScanJobID,
			SearchTag:       item.SearchTag,
			ExternalID:      item.ExternalID,
			Permalink:       item.Permalink,
			Author:          item.Author,
			AuthorVerified:  item.AuthorVerified,
			FollowerCount:   item.FollowerCount,
			Text:            item.Text,
			Priority:        item.Priority,
			LikeCount:       item.LikeCount,
			ReplyCount:      item.ReplyCount,
			EngagementScore: item.EngagementScore,
			PlacementScore:  item.PlacementScore,
			Source:          string(item.Source),
			Replies:         toReplyEntities(item.Replies),
			CreateAt:        now,
		})
	}
	if err := u.repo.ReplaceForViralScan(ctx, req.TenantID, req.OwnerUID, req.PersonaID, req.CopyMissionID, req.ScanJobID, entities); err != nil {
		return 0, err
	}
	return len(entities), nil
}

func (u *scanPostUseCase) ClearPlacementScan(ctx context.Context, tenantID, ownerUID, brandID, topicID string) error {
	if err := requireListActor(tenantID, ownerUID, brandID, topicID); err != nil {
		return err
	}
	return u.repo.ClearForPlacementScan(ctx, tenantID, ownerUID, brandID, topicID)
}

func (u *scanPostUseCase) UpsertScanCheckpoint(ctx context.Context, req domusecase.CheckpointRequest) (int, error) {
	if err := requireListActor(req.TenantID, req.OwnerUID, req.BrandID, req.TopicID); err != nil {
		return 0, err
	}
	entities := placementCandidatesToEntities(req.TenantID, req.OwnerUID, req.BrandID, req.TopicID, req.GraphID, req.ScanJobID, req.Posts)
	return u.repo.UpsertBatchForScan(ctx, req.TenantID, req.OwnerUID, req.BrandID, req.TopicID, entities)
}

func (u *scanPostUseCase) FinalizeScan(ctx context.Context, req domusecase.ReplaceRequest) (int, error) {
	if err := requireListActor(req.TenantID, req.OwnerUID, req.BrandID, req.TopicID); err != nil {
		return 0, err
	}
	entities := placementCandidatesToEntities(req.TenantID, req.OwnerUID, req.BrandID, req.TopicID, req.GraphID, req.ScanJobID, req.Posts)
	count, err := u.repo.UpsertBatchForScan(ctx, req.TenantID, req.OwnerUID, req.BrandID, req.TopicID, entities)
	if err != nil {
		return 0, err
	}
	keep := make([]string, 0, len(entities))
	for _, item := range entities {
		if strings.TrimSpace(item.Permalink) != "" {
			keep = append(keep, item.Permalink)
		}
	}
	if err := u.repo.PruneScanJobPosts(ctx, req.TenantID, req.OwnerUID, req.BrandID, req.TopicID, req.ScanJobID, keep); err != nil {
		return count, err
	}
	return count, nil
}

func (u *scanPostUseCase) ReplaceFromScan(ctx context.Context, req domusecase.ReplaceRequest) (int, error) {
	if err := requireListActor(req.TenantID, req.OwnerUID, req.BrandID, req.TopicID); err != nil {
		return 0, err
	}
	entities := placementCandidatesToEntities(req.TenantID, req.OwnerUID, req.BrandID, req.TopicID, req.GraphID, req.ScanJobID, req.Posts)
	if err := u.repo.ReplaceForScan(ctx, req.TenantID, req.OwnerUID, req.BrandID, req.ScanJobID, entities); err != nil {
		return 0, err
	}
	return len(entities), nil
}

func placementCandidatesToEntities(tenantID, ownerUID, brandID, topicID, graphID, scanJobID string, posts []placement.ScanCandidate) []entity.ScanPost {
	now := clock.NowUnixNano()
	entities := make([]entity.ScanPost, 0, len(posts))
	for _, item := range posts {
		if strings.TrimSpace(item.Permalink) == "" {
			continue
		}
		entities = append(entities, entity.ScanPost{
			ID:              uuid.NewString(),
			TenantID:        tenantID,
			OwnerUID:        ownerUID,
			BrandID:         brandID,
			TopicID:         strings.TrimSpace(topicID),
			Flow:            entity.FlowPlacement,
			GraphID:         graphID,
			ScanJobID:       scanJobID,
			GraphNodeID:     item.GraphNodeID,
			SearchTag:       item.SearchTag,
			QueryDimension:  string(item.QueryDimension),
			ExternalID:      item.ExternalID,
			Permalink:       item.Permalink,
			Author:          item.Author,
			Text:            item.Text,
			Priority:        item.Priority,
			PlacementScore:  item.PlacementScore,
			ProductFitScore: item.ProductFitScore,
			SolvedByProduct: item.SolvedByProduct,
			Source:          string(item.Source),
			OutreachStatus:  entity.OutreachStatusPending,
			PostedAt:        strings.TrimSpace(item.PostedAt),
			Replies:         toReplyEntities(item.Replies),
			CreateAt:        now,
		})
	}
	return entities
}

func (u *scanPostUseCase) Get(ctx context.Context, tenantID, ownerUID, brandID, postID string) (*domusecase.ScanPostSummary, error) {
	if err := requireActor(tenantID, ownerUID, brandID); err != nil {
		return nil, err
	}
	postID = strings.TrimSpace(postID)
	if postID == "" {
		return nil, errMissingBrand()
	}
	item, err := u.repo.Get(ctx, tenantID, ownerUID, brandID, postID)
	if err != nil {
		return nil, err
	}
	summary := toSummary(*item)
	return &summary, nil
}

func (u *scanPostUseCase) UpdateOutreach(ctx context.Context, req domusecase.UpdateOutreachRequest) (*domusecase.ScanPostSummary, error) {
	if err := requireActor(req.TenantID, req.OwnerUID, req.BrandID); err != nil {
		return nil, err
	}
	postID := strings.TrimSpace(req.PostID)
	if postID == "" {
		return nil, errMissingBrand()
	}
	status := strings.TrimSpace(req.Status)
	if status == "" {
		return nil, app.For(code.Brand).InputMissingRequired("outreach status is required")
	}
	item, err := u.repo.UpdateOutreach(ctx, req.TenantID, req.OwnerUID, req.BrandID, postID, entity.OutreachPatch{
		Status:             status,
		PublishedReplyID:   req.PublishedReplyID,
		PublishedPermalink: req.PublishedPermalink,
	})
	if err != nil {
		return nil, err
	}
	summary := toSummary(*item)
	return &summary, nil
}

func (u *scanPostUseCase) Delete(ctx context.Context, tenantID, ownerUID, brandID, topicID, postID string) error {
	if err := requireListActor(tenantID, ownerUID, brandID, topicID); err != nil {
		return err
	}
	postID = strings.TrimSpace(postID)
	if postID == "" {
		return app.For(code.Brand).InputMissingRequired("scan post id is required")
	}
	return u.repo.Delete(ctx, tenantID, ownerUID, brandID, topicID, postID)
}

func (u *scanPostUseCase) DeleteMany(ctx context.Context, tenantID, ownerUID, brandID, topicID string, postIDs []string) (int, error) {
	if err := requireListActor(tenantID, ownerUID, brandID, topicID); err != nil {
		return 0, err
	}
	ids := make([]string, 0, len(postIDs))
	seen := map[string]struct{}{}
	for _, postID := range postIDs {
		id := strings.TrimSpace(postID)
		if id == "" {
			continue
		}
		if _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		ids = append(ids, id)
	}
	if len(ids) == 0 {
		return 0, app.For(code.Brand).InputMissingRequired("post_ids is required")
	}
	return u.repo.DeleteMany(ctx, tenantID, ownerUID, brandID, topicID, ids)
}

func (u *scanPostUseCase) GetForPersona(ctx context.Context, tenantID, ownerUID, personaID, postID string) (*domusecase.ScanPostSummary, error) {
	if err := requireViralActor(tenantID, ownerUID, personaID); err != nil {
		return nil, err
	}
	postID = strings.TrimSpace(postID)
	if postID == "" {
		return nil, errMissingPersona()
	}
	item, err := u.repo.GetForPersona(ctx, tenantID, ownerUID, personaID, postID)
	if err != nil {
		return nil, err
	}
	summary := toSummary(*item)
	return &summary, nil
}

func (u *scanPostUseCase) ListForPersona(ctx context.Context, req domusecase.PersonaListRequest) ([]domusecase.ScanPostSummary, error) {
	if err := requireViralActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return nil, err
	}
	items, err := u.repo.ListForPersona(ctx, req.TenantID, req.OwnerUID, domrepo.PersonaListFilter{
		PersonaID:     req.PersonaID,
		CopyMissionID: req.CopyMissionID,
		Flow:          entity.FlowViral,
		Limit:         req.Limit,
	})
	if err != nil {
		return nil, err
	}
	out := make([]domusecase.ScanPostSummary, 0, len(items))
	for _, item := range items {
		out = append(out, toSummary(item))
	}
	return out, nil
}

func (u *scanPostUseCase) List(ctx context.Context, req domusecase.ListRequest) ([]domusecase.ScanPostSummary, error) {
	if err := requireListActor(req.TenantID, req.OwnerUID, req.BrandID, req.TopicID); err != nil {
		return nil, err
	}
	items, err := u.repo.List(ctx, req.TenantID, req.OwnerUID, domrepo.ListFilter{
		BrandID:       req.BrandID,
		TopicID:       req.TopicID,
		Priority:      req.Priority,
		ProductFitMin: req.ProductFitMin,
		Recent7dOnly:  req.Recent7dOnly,
		Limit:         req.Limit,
	})
	if err != nil {
		return nil, err
	}
	out := make([]domusecase.ScanPostSummary, 0, len(items))
	for _, item := range items {
		out = append(out, toSummary(item))
	}
	return out, nil
}

func requireActor(tenantID, ownerUID, brandID string) error {
	if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
		return errMissingActor()
	}
	if strings.TrimSpace(brandID) == "" {
		return errMissingBrand()
	}
	return nil
}

func requireListActor(tenantID, ownerUID, brandID, topicID string) error {
	if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
		return errMissingActor()
	}
	if strings.TrimSpace(topicID) == "" && strings.TrimSpace(brandID) == "" {
		return errMissingBrand()
	}
	return nil
}

func requireViralActor(tenantID, ownerUID, personaID string) error {
	if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
		return errMissingActor()
	}
	if strings.TrimSpace(personaID) == "" {
		return errMissingPersona()
	}
	return nil
}

func errMissingPersona() error {
	return app.For(code.Persona).InputMissingRequired("persona_id is required")
}

func toReplyEntities(replies []placement.ReplyCandidate) []entity.ScanReply {
	if len(replies) == 0 {
		return nil
	}
	out := make([]entity.ScanReply, 0, len(replies))
	for _, reply := range replies {
		out = append(out, entity.ScanReply{
			ExternalID: reply.ExternalID,
			Author:     reply.Author,
			Text:       reply.Text,
			Permalink:  reply.Permalink,
			LikeCount:  reply.LikeCount,
			PostedAt:   reply.PostedAt,
		})
	}
	return out
}

func toReplySummaries(replies []entity.ScanReply) []domusecase.ScanReplySummary {
	if len(replies) == 0 {
		return nil
	}
	out := make([]domusecase.ScanReplySummary, 0, len(replies))
	for _, reply := range replies {
		out = append(out, domusecase.ScanReplySummary{
			ExternalID: reply.ExternalID,
			Author:     reply.Author,
			Text:       reply.Text,
			Permalink:  reply.Permalink,
			LikeCount:  reply.LikeCount,
			PostedAt:   reply.PostedAt,
		})
	}
	return out
}

func toSummary(item entity.ScanPost) domusecase.ScanPostSummary {
	return domusecase.ScanPostSummary{
		ID:                 item.ID,
		BrandID:            libmongo.ResolveBrandID(item.BrandID, item.LegacyPersonaID),
		PersonaID:          strings.TrimSpace(item.LegacyPersonaID),
		CopyMissionID:      item.CopyMissionID,
		Flow:               item.Flow,
		GraphNodeID:        item.GraphNodeID,
		SearchTag:          item.SearchTag,
		QueryDimension:     item.QueryDimension,
		ExternalID:         item.ExternalID,
		Permalink:          item.Permalink,
		Author:             item.Author,
		AuthorVerified:     item.AuthorVerified,
		FollowerCount:      item.FollowerCount,
		Text:               item.Text,
		Priority:           item.Priority,
		LikeCount:          item.LikeCount,
		ReplyCount:         item.ReplyCount,
		EngagementScore:    item.EngagementScore,
		PlacementScore:     item.PlacementScore,
		ProductFitScore:    item.ProductFitScore,
		SolvedByProduct:    item.SolvedByProduct,
		Source:             item.Source,
		ScanJobID:          item.ScanJobID,
		OutreachStatus:     item.OutreachStatus,
		PublishedReplyID:   item.PublishedReplyID,
		PublishedPermalink: item.PublishedPermalink,
		OutreachUpdateAt:   item.OutreachUpdateAt,
		PostedAt:           item.PostedAt,
		Replies:            toReplySummaries(item.Replies),
		CreateAt:           item.CreateAt,
	}
}
