package usecase

import (
	"context"
	"strings"

	"haixun-backend/internal/library/clock"
	libcopy "haixun-backend/internal/library/copymission"
	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	"haixun-backend/internal/library/threadspost"
	"haixun-backend/internal/model/copy_draft/domain/entity"
	domrepo "haixun-backend/internal/model/copy_draft/domain/repository"
	domusecase "haixun-backend/internal/model/copy_draft/domain/usecase"

	"github.com/google/uuid"
	goredis "github.com/redis/go-redis/v9"
)

const defaultIdleDraftKeep = 40

type copyDraftUseCase struct {
	repo  domrepo.Repository
	redis *goredis.Client
}

func NewUseCase(repo domrepo.Repository, redis *goredis.Client) domusecase.UseCase {
	return &copyDraftUseCase{repo: repo, redis: redis}
}

func (u *copyDraftUseCase) Create(ctx context.Context, req domusecase.CreateRequest) (*domusecase.CopyDraftSummary, error) {
	if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return nil, err
	}
	text := threadspost.FormatDraftText(req.Text)
	if text == "" {
		return nil, app.For(code.Persona).InputMissingRequired("draft text is required")
	}
	draftType := strings.TrimSpace(req.DraftType)
	if draftType == "" {
		draftType = entity.DraftTypeViralReplica
	}
	item := &entity.CopyDraft{
		ID:                  uuid.NewString(),
		TenantID:            req.TenantID,
		OwnerUID:            req.OwnerUID,
		PersonaID:           req.PersonaID,
		ContentPlanID:       strings.TrimSpace(req.ContentPlanID),
		CopyMissionID:       strings.TrimSpace(req.CopyMissionID),
		ScanPostID:          strings.TrimSpace(req.ScanPostID),
		FormulaID:           strings.TrimSpace(req.FormulaID),
		DraftType:           draftType,
		SortOrder:           req.SortOrder,
		Text:                text,
		TopicTag:            normalizeTopicTag(req.TopicTag),
		Angle:               strings.TrimSpace(req.Angle),
		Hook:                strings.TrimSpace(req.Hook),
		Rationale:           strings.TrimSpace(req.Rationale),
		ReferenceNotes:      strings.TrimSpace(req.ReferenceNotes),
		Sources:             req.Sources,
		AiScore:             clampScore(req.AiScore),
		FormulaScore:        clampScore(req.FormulaScore),
		BrandFitScore:       clampScore(req.BrandFitScore),
		RiskScore:           clampScore(req.RiskScore),
		SimilarityScore:     clampScore(req.SimilarityScore),
		EngagementPotential: clampScore(req.EngagementPotential),
		FreshnessScore:      clampScore(req.FreshnessScore),
		ReviewSuggestion:    strings.TrimSpace(req.ReviewSuggestion),
		Status:              "pending",
		CreateAt:            clock.NowUnixNano(),
	}
	if err := u.repo.Create(ctx, item); err != nil {
		return nil, err
	}
	u.pruneIdleDraftsBestEffort(ctx, req.TenantID, req.OwnerUID, req.PersonaID)
	summary := toSummary(*item)
	return &summary, nil
}

func (u *copyDraftUseCase) CreateMany(ctx context.Context, req domusecase.CreateManyRequest) ([]domusecase.CopyDraftSummary, error) {
	if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return nil, err
	}
	if len(req.Drafts) == 0 {
		return nil, app.For(code.Persona).InputMissingRequired("drafts is required")
	}
	items := make([]*entity.CopyDraft, 0, len(req.Drafts))
	for idx, draftReq := range req.Drafts {
		text := threadspost.FormatDraftText(draftReq.Text)
		if text == "" {
			continue
		}
		draftType := strings.TrimSpace(draftReq.DraftType)
		if draftType == "" {
			draftType = entity.DraftTypeMatrix
		}
		sortOrder := draftReq.SortOrder
		if sortOrder == 0 {
			sortOrder = idx + 1
		}
		items = append(items, &entity.CopyDraft{
			ID:                  uuid.NewString(),
			TenantID:            req.TenantID,
			OwnerUID:            req.OwnerUID,
			PersonaID:           req.PersonaID,
			ContentPlanID:       strings.TrimSpace(draftReq.ContentPlanID),
			CopyMissionID:       strings.TrimSpace(draftReq.CopyMissionID),
			ScanPostID:          strings.TrimSpace(draftReq.ScanPostID),
			DraftType:           draftType,
			SortOrder:           sortOrder,
			Text:                text,
			TopicTag:            normalizeTopicTag(draftReq.TopicTag),
			Angle:               strings.TrimSpace(draftReq.Angle),
			Hook:                strings.TrimSpace(draftReq.Hook),
			Rationale:           strings.TrimSpace(draftReq.Rationale),
			ReferenceNotes:      strings.TrimSpace(draftReq.ReferenceNotes),
			Sources:             draftReq.Sources,
			AiScore:             clampScore(draftReq.AiScore),
			FormulaScore:        clampScore(draftReq.FormulaScore),
			BrandFitScore:       clampScore(draftReq.BrandFitScore),
			RiskScore:           clampScore(draftReq.RiskScore),
			SimilarityScore:     clampScore(draftReq.SimilarityScore),
			EngagementPotential: clampScore(draftReq.EngagementPotential),
			FreshnessScore:      clampScore(draftReq.FreshnessScore),
			ReviewSuggestion:    strings.TrimSpace(draftReq.ReviewSuggestion),
			Status:              "pending",
			CreateAt:            clock.NowUnixNano(),
		})
	}
	if len(items) == 0 {
		return nil, app.For(code.Persona).InputMissingRequired("draft text is required")
	}
	if err := u.repo.CreateMany(ctx, items); err != nil {
		return nil, err
	}
	u.pruneIdleDraftsBestEffort(ctx, req.TenantID, req.OwnerUID, req.PersonaID)
	out := make([]domusecase.CopyDraftSummary, 0, len(items))
	for _, item := range items {
		out = append(out, toSummary(*item))
	}
	return out, nil
}

func (u *copyDraftUseCase) Get(ctx context.Context, tenantID, ownerUID, personaID, draftID string) (*domusecase.CopyDraftSummary, error) {
	if err := requireActor(tenantID, ownerUID, personaID); err != nil {
		return nil, err
	}
	item, err := u.repo.Get(ctx, tenantID, ownerUID, personaID, draftID)
	if err != nil {
		return nil, err
	}
	summary := toSummary(*item)
	return &summary, nil
}

func (u *copyDraftUseCase) Update(ctx context.Context, req domusecase.UpdateRequest) (*domusecase.CopyDraftSummary, error) {
	if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return nil, err
	}
	draftID := strings.TrimSpace(req.DraftID)
	if draftID == "" {
		return nil, app.For(code.Persona).InputMissingRequired("draft_id is required")
	}
	patch := map[string]interface{}{}
	if req.Patch.Text != nil {
		text := strings.TrimSpace(*req.Patch.Text)
		if text == "" {
			return nil, app.For(code.Persona).InputMissingRequired("draft text cannot be empty")
		}
		patch["text"] = text
	}
	if req.Patch.TopicTag != nil {
		patch["topic_tag"] = normalizeTopicTag(*req.Patch.TopicTag)
	}
	if req.Patch.Hook != nil {
		patch["hook"] = strings.TrimSpace(*req.Patch.Hook)
	}
	if req.Patch.Angle != nil {
		patch["angle"] = strings.TrimSpace(*req.Patch.Angle)
	}
	if req.Patch.Rationale != nil {
		patch["rationale"] = strings.TrimSpace(*req.Patch.Rationale)
	}
	if req.Patch.Status != nil {
		status := strings.TrimSpace(*req.Patch.Status)
		if status != "" && status != "pending" && status != "ready" && status != "rejected" && status != "scheduled" {
			return nil, app.For(code.Persona).InputMissingRequired("status must be pending, ready, rejected or scheduled")
		}
		if status != "" {
			patch["status"] = status
		}
	}
	if len(patch) == 0 {
		return nil, app.For(code.Persona).InputMissingRequired("no fields to update")
	}
	item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.PersonaID, draftID, patch)
	if err != nil {
		return nil, err
	}
	summary := toSummary(*item)
	return &summary, nil
}

func (u *copyDraftUseCase) MarkPublished(ctx context.Context, req domusecase.MarkPublishedRequest) (*domusecase.CopyDraftSummary, error) {
	if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return nil, err
	}
	draftID := strings.TrimSpace(req.DraftID)
	mediaID := strings.TrimSpace(req.MediaID)
	if draftID == "" || mediaID == "" {
		return nil, app.For(code.Persona).InputMissingRequired("draft_id and media_id are required")
	}
	now := clock.NowUnixNano()
	patch := map[string]interface{}{
		"status":              "published",
		"publish_queue_id":    "",
		"published_media_id":  mediaID,
		"published_permalink": strings.TrimSpace(req.Permalink),
		"published_at":        now,
	}
	item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.PersonaID, draftID, patch)
	if err != nil {
		return nil, err
	}
	summary := toSummary(*item)
	return &summary, nil
}

func (u *copyDraftUseCase) MarkScheduled(ctx context.Context, req domusecase.MarkScheduledRequest) (*domusecase.CopyDraftSummary, error) {
	if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return nil, err
	}
	draftID := strings.TrimSpace(req.DraftID)
	queueID := strings.TrimSpace(req.QueueID)
	if draftID == "" || queueID == "" {
		return nil, app.For(code.Persona).InputMissingRequired("draft_id and queue_id are required")
	}
	item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.PersonaID, draftID, map[string]interface{}{
		"status":           "scheduled",
		"publish_queue_id": queueID,
	})
	if err != nil {
		return nil, err
	}
	summary := toSummary(*item)
	return &summary, nil
}

func (u *copyDraftUseCase) ReplaceMissionMatrix(
	ctx context.Context,
	tenantID, ownerUID, personaID, missionID string,
	drafts []domusecase.CreateRequest,
) ([]domusecase.CopyDraftSummary, error) {
	if err := requireActor(tenantID, ownerUID, personaID); err != nil {
		return nil, err
	}
	missionID = strings.TrimSpace(missionID)
	if missionID == "" {
		return nil, app.For(code.Persona).InputMissingRequired("copy_mission_id is required")
	}
	var out []domusecase.CopyDraftSummary
	err := libcopy.WithMatrixReplaceLock(ctx, u.redis, tenantID, missionID, ownerUID, func() error {
		items, buildErr := buildMatrixDraftEntities(tenantID, ownerUID, personaID, missionID, drafts)
		if buildErr != nil {
			return buildErr
		}
		if replaceErr := u.repo.ReplaceMissionMatrix(ctx, tenantID, ownerUID, personaID, missionID, items); replaceErr != nil {
			return replaceErr
		}
		out = summariesFromEntities(items)
		return nil
	})
	if err != nil {
		if strings.Contains(err.Error(), "already in progress") {
			return nil, app.For(code.Persona).ResInvalidState("內容矩陣正在寫入中，請稍後再試")
		}
		return nil, err
	}
	return out, nil
}

func buildMatrixDraftEntities(
	tenantID, ownerUID, personaID, missionID string,
	drafts []domusecase.CreateRequest,
) ([]*entity.CopyDraft, error) {
	if len(drafts) == 0 {
		return nil, nil
	}
	batchID := uuid.NewString()
	now := clock.NowUnixNano()
	items := make([]*entity.CopyDraft, 0, len(drafts))
	for idx, draftReq := range drafts {
		text := threadspost.FormatDraftText(draftReq.Text)
		if text == "" {
			continue
		}
		sortOrder := draftReq.SortOrder
		if sortOrder == 0 {
			sortOrder = idx + 1
		}
		items = append(items, &entity.CopyDraft{
			ID:             uuid.NewString(),
			TenantID:       tenantID,
			OwnerUID:       ownerUID,
			PersonaID:      personaID,
			CopyMissionID:  missionID,
			ScanPostID:     strings.TrimSpace(draftReq.ScanPostID),
			DraftType:      entity.DraftTypeMatrix,
			MatrixBatchID:  batchID,
			SortOrder:      sortOrder,
			Text:           text,
			TopicTag:       normalizeTopicTag(draftReq.TopicTag),
			Angle:          strings.TrimSpace(draftReq.Angle),
			Hook:           strings.TrimSpace(draftReq.Hook),
			Rationale:      strings.TrimSpace(draftReq.Rationale),
			ReferenceNotes: strings.TrimSpace(draftReq.ReferenceNotes),
			Sources:        draftReq.Sources,
			Status:         "pending",
			CreateAt:       now,
		})
	}
	if len(items) == 0 {
		return nil, app.For(code.Persona).InputMissingRequired("draft text is required")
	}
	return items, nil
}

func summariesFromEntities(items []*entity.CopyDraft) []domusecase.CopyDraftSummary {
	out := make([]domusecase.CopyDraftSummary, 0, len(items))
	for _, item := range items {
		if item == nil {
			continue
		}
		out = append(out, toSummary(*item))
	}
	return out
}

func (u *copyDraftUseCase) Delete(ctx context.Context, tenantID, ownerUID, personaID, draftID string) error {
	if err := requireActor(tenantID, ownerUID, personaID); err != nil {
		return err
	}
	draftID = strings.TrimSpace(draftID)
	if draftID == "" {
		return app.For(code.Persona).InputMissingRequired("draft_id is required")
	}
	return u.repo.Delete(ctx, tenantID, ownerUID, personaID, draftID)
}

func (u *copyDraftUseCase) DeleteMissionMatrixDrafts(
	ctx context.Context,
	req domusecase.DeleteMissionMatrixDraftsRequest,
) (int, error) {
	if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return 0, err
	}
	missionID := strings.TrimSpace(req.MissionID)
	if missionID == "" {
		return 0, app.For(code.Persona).InputMissingRequired("copy_mission_id is required")
	}
	count, err := u.repo.DeleteMany(
		ctx,
		req.TenantID,
		req.OwnerUID,
		req.PersonaID,
		missionID,
		entity.DraftTypeMatrix,
		req.DraftIDs,
	)
	if err != nil {
		return 0, err
	}
	if len(req.DraftIDs) > 0 && count == 0 {
		return 0, app.For(code.Persona).ResNotFound("找不到要刪除的矩陣草稿")
	}
	return int(count), nil
}

func (u *copyDraftUseCase) ClearByMission(ctx context.Context, tenantID, ownerUID, personaID, missionID string) error {
	if err := requireActor(tenantID, ownerUID, personaID); err != nil {
		return err
	}
	missionID = strings.TrimSpace(missionID)
	if missionID == "" {
		return app.For(code.Persona).InputMissingRequired("copy_mission_id is required")
	}
	return u.repo.DeleteByMission(ctx, tenantID, ownerUID, personaID, missionID)
}

func (u *copyDraftUseCase) ListByMission(ctx context.Context, tenantID, ownerUID, personaID, missionID string, limit int) ([]domusecase.CopyDraftSummary, error) {
	if err := requireActor(tenantID, ownerUID, personaID); err != nil {
		return nil, err
	}
	items, err := u.repo.ListByMission(ctx, tenantID, ownerUID, personaID, missionID, limit)
	if err != nil {
		return nil, err
	}
	out := make([]domusecase.CopyDraftSummary, 0, len(items))
	for _, item := range items {
		out = append(out, toSummary(item))
	}
	return out, nil
}

func (u *copyDraftUseCase) List(ctx context.Context, tenantID, ownerUID, personaID string, limit int) ([]domusecase.CopyDraftSummary, error) {
	if err := requireActor(tenantID, ownerUID, personaID); err != nil {
		return nil, err
	}
	items, err := u.repo.List(ctx, tenantID, ownerUID, personaID, limit)
	if err != nil {
		return nil, err
	}
	out := make([]domusecase.CopyDraftSummary, 0, len(items))
	for _, item := range items {
		out = append(out, toSummary(item))
	}
	return out, nil
}

func (u *copyDraftUseCase) PruneIdleDrafts(ctx context.Context, tenantID, ownerUID, personaID string, keep int) (int64, error) {
	if err := requireActor(tenantID, ownerUID, personaID); err != nil {
		return 0, err
	}
	if keep <= 0 {
		keep = defaultIdleDraftKeep
	}
	if keep > 200 {
		keep = 200
	}
	return u.repo.PruneOldestPendingBeyond(ctx, tenantID, ownerUID, personaID, keep)
}

func (u *copyDraftUseCase) pruneIdleDraftsBestEffort(ctx context.Context, tenantID, ownerUID, personaID string) {
	_, _ = u.repo.PruneOldestPendingBeyond(ctx, tenantID, ownerUID, personaID, defaultIdleDraftKeep)
}

func (u *copyDraftUseCase) ListInbox(ctx context.Context, req domusecase.InboxListRequest) (*domusecase.InboxListResult, error) {
	if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
		return nil, err
	}
	result, err := u.repo.ListInbox(ctx, domrepo.InboxFilter{
		TenantID:   req.TenantID,
		OwnerUID:   req.OwnerUID,
		PersonaID:  req.PersonaID,
		Status:     req.Status,
		RangeStart: req.RangeStart,
		RangeEnd:   req.RangeEnd,
		Page:       req.Page,
		PageSize:   req.PageSize,
	})
	if err != nil {
		return nil, err
	}
	out := make([]domusecase.CopyDraftSummary, 0, len(result.Items))
	for _, item := range result.Items {
		out = append(out, toSummary(item))
	}
	return &domusecase.InboxListResult{Items: out, Total: result.Total}, nil
}

func requireActor(tenantID, ownerUID, personaID string) error {
	if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
		return app.For(code.Auth).AuthUnauthorized("missing actor")
	}
	if strings.TrimSpace(personaID) == "" {
		return app.For(code.Persona).InputMissingRequired("persona_id is required")
	}
	return nil
}

func clampScore(score int) int {
	if score < 0 {
		return 0
	}
	if score > 100 {
		return 100
	}
	return score
}

func toSummary(item entity.CopyDraft) domusecase.CopyDraftSummary {
	return domusecase.CopyDraftSummary{
		ID:                  item.ID,
		PersonaID:           item.PersonaID,
		ContentPlanID:       item.ContentPlanID,
		CopyMissionID:       item.CopyMissionID,
		ScanPostID:          item.ScanPostID,
		FormulaID:           item.FormulaID,
		DraftType:           item.DraftType,
		SortOrder:           item.SortOrder,
		Text:                item.Text,
		TopicTag:            item.TopicTag,
		Angle:               item.Angle,
		Hook:                item.Hook,
		Rationale:           item.Rationale,
		ReferenceNotes:      item.ReferenceNotes,
		Sources:             item.Sources,
		AiScore:             item.AiScore,
		FormulaScore:        item.FormulaScore,
		BrandFitScore:       item.BrandFitScore,
		RiskScore:           item.RiskScore,
		SimilarityScore:     item.SimilarityScore,
		EngagementPotential: item.EngagementPotential,
		FreshnessScore:      item.FreshnessScore,
		ReviewSuggestion:    item.ReviewSuggestion,
		Status:              item.Status,
		PublishQueueID:      item.PublishQueueID,
		PublishedMediaID:    item.PublishedMediaID,
		PublishedPermalink:  item.PublishedPermalink,
		PublishedAt:         item.PublishedAt,
		CreateAt:            item.CreateAt,
	}
}

func normalizeTopicTag(value string) string {
	value = strings.TrimSpace(value)
	value = strings.TrimPrefix(value, "#")
	value = strings.TrimSpace(value)
	if len([]rune(value)) > 40 {
		runes := []rune(value)
		value = string(runes[:40])
	}
	return value
}
