package usecase

import (
	"context"
	"strings"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	authusecase "haixun-backend/internal/model/auth/domain/usecase"
	"haixun-backend/internal/model/member/domain/entity"
	domrepo "haixun-backend/internal/model/member/domain/repository"
	domusecase "haixun-backend/internal/model/member/domain/usecase"

	"github.com/google/uuid"
	"golang.org/x/crypto/bcrypt"
)

type memberUseCase struct {
	repo   domrepo.Repository
	tokens authusecase.TokenUseCase
}

func NewUseCase(repo domrepo.Repository, tokens authusecase.TokenUseCase) domusecase.UseCase {
	return &memberUseCase{repo: repo, tokens: tokens}
}

func (u *memberUseCase) Register(ctx context.Context, req domusecase.RegisterRequest) (*entity.Member, *domusecase.AuthToken, error) {
	tenantID := strings.TrimSpace(req.TenantID)
	email := normalizeEmail(req.Email)
	if tenantID == "" || email == "" || req.Password == "" {
		return nil, nil, app.For(code.Member).InputMissingRequired("tenant_id, email, and password are required")
	}
	if len(req.Password) < 8 {
		return nil, nil, app.For(code.Member).InputInvalidFormat("password must be at least 8 characters")
	}
	hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		return nil, nil, app.For(code.Member).SysInternal("hash password failed").WithCause(err)
	}
	member, err := u.repo.Create(ctx, &entity.Member{
		TenantID:     tenantID,
		UID:          uuid.NewString(),
		Email:        email,
		DisplayName:  strings.TrimSpace(req.DisplayName),
		Language:     strings.TrimSpace(req.Language),
		Status:       entity.StatusOpen,
		Origin:       entity.OriginNative,
		PasswordHash: string(hash),
		Roles:        []string{"user"},
	})
	if err != nil {
		return nil, nil, err
	}
	token, err := u.issue(ctx, member)
	return member, token, err
}

func (u *memberUseCase) Login(ctx context.Context, req domusecase.LoginRequest) (*entity.Member, *domusecase.AuthToken, error) {
	tenantID := strings.TrimSpace(req.TenantID)
	email := normalizeEmail(req.Email)
	if tenantID == "" || email == "" || req.Password == "" {
		return nil, nil, app.For(code.Auth).InputMissingRequired("tenant_id, email, and password are required")
	}
	member, err := u.repo.FindByEmail(ctx, tenantID, email)
	if err != nil {
		return nil, nil, app.For(code.Auth).AuthUnauthorized("invalid email or password").WithCause(err)
	}
	if member.Status != entity.StatusOpen {
		return nil, nil, app.For(code.Auth).AuthForbidden("member is not active")
	}
	if err := bcrypt.CompareHashAndPassword([]byte(member.PasswordHash), []byte(req.Password)); err != nil {
		return nil, nil, app.For(code.Auth).AuthUnauthorized("invalid email or password")
	}
	token, err := u.issue(ctx, member)
	return member, token, err
}

func (u *memberUseCase) GetByUID(ctx context.Context, tenantID, uid string) (*entity.Member, error) {
	if tenantID == "" || uid == "" {
		return nil, app.For(code.Member).InputMissingRequired("tenant_id and uid are required")
	}
	return u.repo.FindByUID(ctx, tenantID, uid)
}

func (u *memberUseCase) SetActiveThreadsAccountID(ctx context.Context, tenantID, uid, accountID string) error {
	if tenantID == "" || uid == "" {
		return app.For(code.Member).InputMissingRequired("tenant_id and uid are required")
	}
	return u.repo.SetActiveThreadsAccountID(ctx, tenantID, uid, accountID)
}

func (u *memberUseCase) UpdateProfile(ctx context.Context, req domusecase.UpdateProfileRequest) (*entity.Member, error) {
	if req.TenantID == "" || req.UID == "" {
		return nil, app.For(code.Member).InputMissingRequired("tenant_id and uid are required")
	}
	return u.repo.UpdateProfile(ctx, req.TenantID, req.UID, domrepo.ProfileUpdate{
		DisplayName: req.DisplayName,
		Avatar:      req.Avatar,
		Language:    req.Language,
		Currency:    req.Currency,
		Phone:       req.Phone,
	})
}

func (u *memberUseCase) issue(ctx context.Context, member *entity.Member) (*domusecase.AuthToken, error) {
	if u.tokens == nil {
		return nil, app.For(code.Auth).SysNotImplemented("token usecase is not configured")
	}
	pair, err := u.tokens.IssuePair(ctx, authusecase.IssuePairRequest{
		TenantID: member.TenantID,
		UID:      member.UID,
	})
	if err != nil {
		return nil, err
	}
	return &domusecase.AuthToken{
		AccessToken:  pair.AccessToken,
		RefreshToken: pair.RefreshToken,
		ExpiresIn:    pair.ExpiresIn,
		UID:          member.UID,
		TokenType:    pair.TokenType,
	}, nil
}

func normalizeEmail(email string) string {
	return strings.ToLower(strings.TrimSpace(email))
}
