package usecase

import (
	"context"
	"strings"

	"haixun-backend/internal/library/clock"
	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	libthreads "haixun-backend/internal/library/threadsapi"
	"haixun-backend/internal/model/mention_inbox/domain/entity"
	domrepo "haixun-backend/internal/model/mention_inbox/domain/repository"
	domusecase "haixun-backend/internal/model/mention_inbox/domain/usecase"
	threadsaccountdomain "haixun-backend/internal/model/threads_account/domain/usecase"
)

type mentionInboxUseCase struct {
	repo            domrepo.Repository
	threadsAccounts threadsaccountdomain.UseCase
}

func NewUseCase(repo domrepo.Repository, threadsAccounts threadsaccountdomain.UseCase) domusecase.UseCase {
	return &mentionInboxUseCase{repo: repo, threadsAccounts: threadsAccounts}
}

func (u *mentionInboxUseCase) SyncFromAPI(ctx context.Context, req domusecase.SyncRequest) (*domusecase.SyncResult, error) {
	tenantID := strings.TrimSpace(req.TenantID)
	ownerUID := strings.TrimSpace(req.OwnerUID)
	accountID := strings.TrimSpace(req.AccountID)
	if tenantID == "" || ownerUID == "" {
		return nil, app.For(code.Auth).AuthUnauthorized("missing actor")
	}
	if accountID == "" {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("account_id is required")
	}

	account, err := u.threadsAccounts.Get(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}
	token, err := u.threadsAccounts.ResolveAccountAPIAccessToken(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}
	client := libthreads.NewClient(token)
	if !client.Enabled() {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("Threads API 未連線")
	}

	userID := strings.TrimSpace(account.ThreadsUserID)
	if userID == "" {
		if profile, perr := libthreads.GetMeProfile(ctx, token); perr == nil && profile != nil {
			userID = profile.ID.String()
		}
	}
	if userID == "" {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("缺少 threads_user_id")
	}

	perPage := req.Limit
	if perPage <= 0 {
		perPage = 25
	}
	if perPage > 100 {
		perPage = 100
	}
	maxPages := req.MaxPages
	if maxPages <= 0 {
		maxPages = 4
	}
	if maxPages > 20 {
		maxPages = 20
	}

	now := clock.NowUnixNano()
	synced := 0
	ready := 0
	after := ""
	for page := 0; page < maxPages; page++ {
		mentions, nextAfter, err := client.UserMentionsPage(ctx, userID, perPage, after)
		if err != nil {
			return nil, err
		}
		for _, hit := range mentions {
			item := u.buildMentionEntity(tenantID, ownerUID, accountID, hit, now)
			u.fillContext(ctx, client, hit, item)
			u.primeReplyTarget(ctx, client, hit, item)
			if _, err := u.repo.Upsert(ctx, item); err != nil {
				return nil, err
			}
			synced++
			if item.ReplyReady {
				ready++
			}
		}
		if nextAfter == "" || len(mentions) == 0 {
			break
		}
		after = nextAfter
	}

	total, err := u.repo.CountByAccount(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}

	return &domusecase.SyncResult{
		Synced: synced,
		Ready:  ready,
		Total:  total,
	}, nil
}

func (u *mentionInboxUseCase) List(ctx context.Context, req domusecase.ListRequest) (*domusecase.ListResult, error) {
	tenantID := strings.TrimSpace(req.TenantID)
	ownerUID := strings.TrimSpace(req.OwnerUID)
	accountID := strings.TrimSpace(req.AccountID)
	if tenantID == "" || ownerUID == "" {
		return nil, app.For(code.Auth).AuthUnauthorized("missing actor")
	}
	if accountID == "" {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("account_id is required")
	}
	if _, err := u.threadsAccounts.Get(ctx, tenantID, ownerUID, accountID); err != nil {
		return nil, err
	}
	result, err := u.repo.ListByAccount(ctx, tenantID, ownerUID, accountID, req.Page, req.PageSize)
	if err != nil {
		return nil, err
	}
	page, pageSize := normalizeListPage(req.Page, req.PageSize)
	return &domusecase.ListResult{
		List: toSummaries(result.Items),
		Pagination: domusecase.Pagination{
			Total:          result.Total,
			ReadyTotal:     result.ReadyTotal,
			Page:           int64(page),
			PageSize:       int64(pageSize),
			TotalPages:     mentionTotalPages(result.Total, pageSize),
			NewestSyncedAt: result.NewestSyncedAt,
		},
	}, nil
}

func normalizeListPage(page, pageSize int) (int, int) {
	if page <= 0 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = 10
	}
	if pageSize > 50 {
		pageSize = 50
	}
	return page, pageSize
}

func mentionTotalPages(total int64, pageSize int) int64 {
	if total <= 0 {
		return 0
	}
	return (total + int64(pageSize) - 1) / int64(pageSize)
}

func (u *mentionInboxUseCase) PublishReply(ctx context.Context, req domusecase.PublishReplyRequest) (*domusecase.PublishReplyResult, error) {
	tenantID := strings.TrimSpace(req.TenantID)
	ownerUID := strings.TrimSpace(req.OwnerUID)
	accountID := strings.TrimSpace(req.AccountID)
	mediaID := strings.TrimSpace(req.MediaID)
	text := strings.TrimSpace(req.Text)
	if tenantID == "" || ownerUID == "" {
		return nil, app.For(code.Auth).AuthUnauthorized("missing actor")
	}
	if accountID == "" || mediaID == "" {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("account_id and media_id are required")
	}
	if text == "" {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("text is required")
	}

	item, err := u.repo.Get(ctx, tenantID, ownerUID, accountID, mediaID)
	if err != nil {
		return nil, err
	}
	if !item.ReplyReady {
		msg := strings.TrimSpace(item.ReplyReadyMsg)
		if msg == "" {
			msg = "此提及尚未完成回覆註冊，請先重新同步提及清單"
		}
		return nil, app.For(code.ThreadsAccount).InputInvalidFormat(msg)
	}

	account, err := u.threadsAccounts.Get(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}
	token, err := u.threadsAccounts.ResolveAccountAPIAccessToken(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}
	userID := strings.TrimSpace(account.ThreadsUserID)
	if userID == "" {
		if profile, perr := libthreads.GetMeProfile(ctx, token); perr == nil && profile != nil {
			userID = profile.ID.String()
		}
	}
	if userID == "" {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("缺少 threads_user_id")
	}

	result, err := libthreads.PublishReply(ctx, libthreads.PublishReplyInput{
		ThreadsUserID: userID,
		AccessToken:   token,
		ReplyToID:     mediaID,
		Text:          text,
	})
	if err != nil {
		return nil, err
	}
	out := &domusecase.PublishReplyResult{}
	if result != nil {
		out.MediaID = strings.TrimSpace(result.MediaID)
		out.Permalink = strings.TrimSpace(result.Permalink)
	}
	return out, nil
}

func (u *mentionInboxUseCase) buildMentionEntity(
	tenantID, ownerUID, accountID string,
	hit libthreads.MentionItem,
	now int64,
) *entity.Mention {
	mediaID := strings.TrimSpace(hit.ID)
	return &entity.Mention{
		ID:             entity.DocID(accountID, mediaID),
		TenantID:       tenantID,
		OwnerUID:       ownerUID,
		AccountID:      accountID,
		MediaID:        mediaID,
		AuthorUsername: strings.TrimSpace(hit.Username),
		Text:           strings.TrimSpace(hit.Text),
		Permalink:      strings.TrimSpace(hit.Permalink),
		Shortcode:      shortcodeFromPermalink(hit.Permalink),
		Timestamp:      strings.TrimSpace(hit.Timestamp),
		MediaType:      strings.TrimSpace(hit.MediaType),
		IsReply:        hit.IsReply,
		IsQuotePost:    hit.IsQuotePost,
		HasReplies:     hit.HasReplies,
		RootPostID:     strings.TrimSpace(hit.RootPostID),
		ParentID:       strings.TrimSpace(hit.ParentID),
		SyncedAt:       now,
		UpdateAt:       now,
	}
}

func (u *mentionInboxUseCase) fillContext(ctx context.Context, client *libthreads.Client, hit libthreads.MentionItem, item *entity.Mention) {
	mentionText := strings.TrimSpace(hit.Text)
	if !hit.IsReply && !hit.IsQuotePost {
		item.ThreadPostText = mentionText
		return
	}

	rootID := strings.TrimSpace(hit.RootPostID)
	if rootID == "" {
		rootID = strings.TrimSpace(hit.ID)
	}
	if rootID != "" {
		if detail, err := client.GetMediaDetail(ctx, rootID); err == nil && detail != nil {
			item.ThreadPostText = strings.TrimSpace(detail.Text)
		}
	}
	if item.ThreadPostText == "" {
		item.ThreadPostText = mentionText
	}

	parentID := strings.TrimSpace(hit.ParentID)
	mediaID := strings.TrimSpace(hit.ID)
	if hit.IsReply && parentID != "" && parentID != mediaID && parentID != rootID {
		if detail, err := client.GetMediaDetail(ctx, parentID); err == nil && detail != nil {
			item.ParentReplyText = strings.TrimSpace(detail.Text)
		}
	}
}

func (u *mentionInboxUseCase) primeReplyTarget(ctx context.Context, client *libthreads.Client, hit libthreads.MentionItem, item *entity.Mention) {
	permalink := strings.TrimSpace(hit.Permalink)
	mediaID := strings.TrimSpace(hit.ID)
	if permalink == "" {
		item.ReplyReady = false
		item.ReplyReadyMsg = "缺少 permalink，無法註冊回覆目標"
		return
	}
	if err := client.PrimeReplyTargetForPublish(ctx, libthreads.ResolveReplyMediaInput{
		ExternalID: mediaID,
		Permalink:  permalink,
		HintText:   strings.TrimSpace(hit.Text),
	}, mediaID); err != nil {
		item.ReplyReady = false
		item.ReplyReadyMsg = err.Error()
		return
	}
	item.ReplyReady = true
	item.ReplyReadyMsg = ""
}

func shortcodeFromPermalink(permalink string) string {
	permalink = strings.Split(strings.TrimSpace(permalink), "?")[0]
	permalink = strings.Split(permalink, "#")[0]
	parts := strings.Split(permalink, "/post/")
	if len(parts) < 2 {
		return ""
	}
	return strings.TrimSpace(parts[len(parts)-1])
}

func toSummary(item *entity.Mention) domusecase.MentionSummary {
	if item == nil {
		return domusecase.MentionSummary{}
	}
	return domusecase.MentionSummary{
		MediaID:         item.MediaID,
		AuthorUsername:  item.AuthorUsername,
		Text:            item.Text,
		Permalink:       item.Permalink,
		Shortcode:       item.Shortcode,
		Timestamp:       item.Timestamp,
		MediaType:       item.MediaType,
		IsReply:         item.IsReply,
		IsQuotePost:     item.IsQuotePost,
		HasReplies:      item.HasReplies,
		RootPostID:      item.RootPostID,
		ParentID:        item.ParentID,
		ThreadPostText:  item.ThreadPostText,
		ParentReplyText: item.ParentReplyText,
		ReplyReady:      item.ReplyReady,
		ReplyReadyMsg:   item.ReplyReadyMsg,
		SyncedAt:        item.SyncedAt,
	}
}

func toSummaries(items []entity.Mention) []domusecase.MentionSummary {
	out := make([]domusecase.MentionSummary, 0, len(items))
	for i := range items {
		out = append(out, toSummary(&items[i]))
	}
	return out
}
