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/threads_account/domain/entity"
	domrepo "haixun-backend/internal/model/threads_account/domain/repository"
	domusecase "haixun-backend/internal/model/threads_account/domain/usecase"

	"github.com/google/uuid"
)

func (u *threadsAccountUseCase) StartThreadsOAuth(
	ctx context.Context,
	tenantID, ownerUID, accountID string,
) (*domusecase.OAuthStartResult, error) {
	if err := requireActor(tenantID, ownerUID); err != nil {
		return nil, err
	}
	if !u.oauth.Enabled() {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("Threads OAuth 尚未設定（THREADS_APP_ID / THREADS_APP_SECRET / THREADS_REDIRECT_URI）")
	}
	if u.oauthState == nil {
		return nil, app.For(code.ThreadsAccount).DBUnavailable("Redis 未設定，無法保存 OAuth state")
	}

	accountID = strings.TrimSpace(accountID)
	var account *entity.Account
	var err error
	if accountID != "" {
		account, err = u.assertOwned(ctx, tenantID, ownerUID, accountID)
		if err != nil {
			return nil, err
		}
	} else {
		existing, listErr := u.repo.ListByOwner(ctx, tenantID, ownerUID)
		if listErr != nil {
			return nil, listErr
		}
		created, createErr := u.Create(ctx, domusecase.CreateRequest{
			TenantID:    tenantID,
			OwnerUID:    ownerUID,
			DisplayName: "Threads 帳號 " + itoa(len(existing)+1),
			Activate:    false,
		})
		if createErr != nil {
			return nil, createErr
		}
		account, err = u.assertOwned(ctx, tenantID, ownerUID, created.ID)
		if err != nil {
			return nil, err
		}
	}

	state := uuid.NewString()
	if err := u.oauthState.Save(ctx, state, domrepo.OAuthStatePayload{
		TenantID:  tenantID,
		OwnerUID:  ownerUID,
		AccountID: account.ID,
	}); err != nil {
		return nil, err
	}
	authorizeURL, err := libthreads.BuildAuthorizeURL(u.oauth, state)
	if err != nil {
		return nil, err
	}
	redirectURI := strings.TrimSpace(u.oauth.RedirectURI)
	if !strings.HasPrefix(strings.ToLower(redirectURI), "https://") {
		recordOAuthDebug("error", "start", account.ID, "redirect_uri must be https:// (Meta error 1349187): "+redirectURI)
		return nil, app.For(code.ThreadsAccount).InputInvalidFormat("THREADS_REDIRECT_URI 必須為 https://（Meta 會拒絕 http://，錯誤碼 1349187）")
	}
	recordOAuthDebug("info", "start", account.ID, "redirect_uri="+redirectURI)
	return &domusecase.OAuthStartResult{
		AuthorizeURL: authorizeURL,
		State:        state,
		AccountID:    account.ID,
	}, nil
}

func (u *threadsAccountUseCase) CompleteThreadsOAuthCallback(
	ctx context.Context,
	authCode, state string,
) (*domusecase.OAuthCallbackResult, error) {
	if !u.oauth.Enabled() {
		return nil, app.For(code.ThreadsAccount).InputMissingRequired("Threads OAuth 尚未設定")
	}
	if u.oauthState == nil {
		return nil, app.For(code.ThreadsAccount).DBUnavailable("Redis 未設定")
	}
	recordOAuthDebug("info", "callback", "", "received code len="+itoa(len(strings.TrimSpace(authCode)))+" state len="+itoa(len(strings.TrimSpace(state))))
	payload, err := u.oauthState.Consume(ctx, state)
	if err != nil {
		recordOAuthDebug("error", "consume_state", "", err.Error())
		return nil, err
	}
	short, err := libthreads.ExchangeCodeForToken(ctx, u.oauth, authCode)
	if err != nil {
		recordOAuthDebug("error", "exchange_code", payload.AccountID, err.Error())
		return nil, err
	}
	recordOAuthDebug("info", "exchange_code", payload.AccountID, "short-lived ok expires_in="+itoa(short.ExpiresIn))
	longLived, err := libthreads.ExchangeLongLivedToken(ctx, u.oauth, short.AccessToken)
	if err != nil {
		recordOAuthDebug("warn", "long_lived", payload.AccountID, "fallback short-lived: "+err.Error())
		longLived = short
	} else {
		recordOAuthDebug("info", "long_lived", payload.AccountID, "ok expires_in="+itoa(longLived.ExpiresIn))
	}
	account, err := u.assertOwned(ctx, payload.TenantID, payload.OwnerUID, payload.AccountID)
	if err != nil {
		return nil, err
	}

	profile, err := libthreads.GetMeProfile(ctx, longLived.AccessToken)
	threadsUserID := ""
	username := ""
	displayName := ""
	if err != nil {
		threadsUserID = libthreads.ThreadsUserIDFromToken(short, longLived)
		if threadsUserID == "" {
			recordOAuthDebug("error", "profile_me", payload.AccountID, err.Error())
			if libthreads.IsPermissionError(err) {
				return nil, app.For(code.ThreadsAccount).AuthForbidden(
					"Threads API 需要 threads_basic：請到 Meta App Dashboard → 應用程式角色，把你的 Threads 帳號加為「測試人員」或管理員，並確認已啟用「Access the Threads API」",
				)
			}
			return nil, err
		}
		recordOAuthDebug("warn", "profile_me", payload.AccountID, "fallback token user_id="+threadsUserID+": "+err.Error())
		displayName = strings.TrimSpace(account.DisplayName)
		if displayName == "" {
			displayName = "Threads " + threadsUserID
		}
	} else {
		threadsUserID = profile.ID.String()
		username = profile.Username
		recordOAuthDebug("info", "profile_me", payload.AccountID, "username="+username+" threads_user_id="+threadsUserID)
		displayName = strings.TrimSpace(profile.Name)
		if displayName == "" {
			displayName = "@" + strings.TrimPrefix(username, "@")
		}
	}

	if err := u.assertNoAPIBindingConflict(ctx, payload.TenantID, payload.OwnerUID, account.ID, threadsUserID); err != nil {
		return nil, err
	}

	updated, err := u.repo.UpdateAPIProfile(
		ctx,
		payload.TenantID,
		payload.OwnerUID,
		account.ID,
		username,
		threadsUserID,
		displayName,
	)
	if err != nil {
		return nil, err
	}
	expiresAt := int64(0)
	if longLived.ExpiresIn >= 300 {
		expiresAt = libthreads.NsFromNow(longLived.ExpiresIn)
	} else if longLived.ExpiresIn > 0 {
		recordOAuthDebug("warn", "expires_in", payload.AccountID, "suspicious expires_in="+itoa(longLived.ExpiresIn)+"s; store without expiry deadline")
	}
	if _, err := u.secretsRepo.SaveAPIAccessToken(ctx, updated.ID, longLived.AccessToken, expiresAt); err != nil {
		recordOAuthDebug("error", "save_token", updated.ID, err.Error())
		return nil, err
	}
	recordOAuthDebug("info", "success", updated.ID, "api_connected=true username="+updated.Username)
	if err := u.members.SetActiveThreadsAccountID(ctx, payload.TenantID, payload.OwnerUID, updated.ID); err != nil {
		return nil, err
	}

	redirectURL := strings.TrimSpace(u.oauth.SuccessRedirect)
	if redirectURL == "" {
		redirectURL = "/"
	}
	sep := "?"
	if strings.Contains(redirectURL, "?") {
		sep = "&"
	}
	return &domusecase.OAuthCallbackResult{
		AccountID:     updated.ID,
		Username:      updated.Username,
		ThreadsUserID: updated.ThreadsUserID,
		RedirectURL:   redirectURL + sep + "threads_oauth=ok&account_id=" + updated.ID,
	}, nil
}

func (u *threadsAccountUseCase) DisconnectThreadsAPI(
	ctx context.Context,
	tenantID, ownerUID, accountID string,
) (*domusecase.AccountSummary, error) {
	account, err := u.assertOwned(ctx, tenantID, ownerUID, accountID)
	if err != nil {
		return nil, err
	}
	if err := u.secretsRepo.ClearAPIAccessToken(ctx, account.ID); err != nil {
		return nil, err
	}
	if _, err := u.repo.ClearAPIProfile(ctx, tenantID, ownerUID, account.ID); err != nil {
		return nil, err
	}
	return u.toSummary(ctx, account)
}

func (u *threadsAccountUseCase) assertNoAPIBindingConflict(
	ctx context.Context,
	tenantID, ownerUID, accountID, threadsUserID string,
) error {
	dup, err := u.repo.FindByThreadsUserID(ctx, tenantID, ownerUID, threadsUserID)
	if err != nil || dup == nil || dup.ID == accountID {
		return nil
	}
	secrets, err := u.secretsRepo.FindByAccountID(ctx, dup.ID)
	if err != nil {
		return err
	}
	if isValidAPIToken(secrets) {
		label := dup.DisplayName
		if label == "" && dup.Username != "" {
			label = "@" + dup.Username
		}
		if label == "" {
			label = dup.ID
		}
		return app.For(code.ThreadsAccount).ResConflict(
			"此 Threads 帳號已綁定在「" + label + "」；請先中斷該帳號 API，或選該帳號按「連線選中帳號」重新授權",
		)
	}
	if _, err := u.repo.ClearAPIProfile(ctx, tenantID, ownerUID, dup.ID); err != nil {
		return err
	}
	return nil
}

func isValidAPIToken(secrets *entity.Secrets) bool {
	if secrets == nil {
		return false
	}
	token := strings.TrimSpace(secrets.APIAccessToken)
	if token == "" {
		return false
	}
	if secrets.APITokenExpiresAt > 0 && secrets.APITokenExpiresAt <= clock.NowUnixNano() {
		return false
	}
	return true
}
