package use_cases

import (
	"context"
	"encoding/hex"
	"time"

	"github.com/rwadurian/mpc-system/pkg/crypto"
	"github.com/rwadurian/mpc-system/services/account/application/ports"
	"github.com/rwadurian/mpc-system/services/account/domain/entities"
	"github.com/rwadurian/mpc-system/services/account/domain/repositories"
	"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
)

// LoginError represents a login error
type LoginError struct {
	Code    string
	Message string
}

func (e *LoginError) Error() string {
	return e.Message
}

var (
	ErrInvalidCredentials = &LoginError{Code: "INVALID_CREDENTIALS", Message: "invalid username or signature"}
	ErrAccountLocked      = &LoginError{Code: "ACCOUNT_LOCKED", Message: "account is locked"}
	ErrAccountSuspended   = &LoginError{Code: "ACCOUNT_SUSPENDED", Message: "account is suspended"}
	ErrSignatureInvalid   = &LoginError{Code: "SIGNATURE_INVALID", Message: "signature verification failed"}
)

// LoginUseCase handles user login with MPC signature verification
type LoginUseCase struct {
	accountRepo    repositories.AccountRepository
	shareRepo      repositories.AccountShareRepository
	tokenService   ports.TokenService
	eventPublisher ports.EventPublisher
}

// NewLoginUseCase creates a new LoginUseCase
func NewLoginUseCase(
	accountRepo repositories.AccountRepository,
	shareRepo repositories.AccountShareRepository,
	tokenService ports.TokenService,
	eventPublisher ports.EventPublisher,
) *LoginUseCase {
	return &LoginUseCase{
		accountRepo:    accountRepo,
		shareRepo:      shareRepo,
		tokenService:   tokenService,
		eventPublisher: eventPublisher,
	}
}

// Execute performs login with signature verification
func (uc *LoginUseCase) Execute(ctx context.Context, input ports.LoginInput) (*ports.LoginOutput, error) {
	// Get account by username
	account, err := uc.accountRepo.GetByUsername(ctx, input.Username)
	if err != nil {
		return nil, ErrInvalidCredentials
	}

	// Check account status
	if !account.CanLogin() {
		switch account.Status.String() {
		case "locked":
			return nil, ErrAccountLocked
		case "suspended":
			return nil, ErrAccountSuspended
		default:
			return nil, entities.ErrAccountNotActive
		}
	}

	// Parse public key
	pubKey, err := crypto.ParsePublicKey(account.PublicKey)
	if err != nil {
		return nil, ErrSignatureInvalid
	}

	// Verify signature (hash the challenge first, as SignMessage does)
	challengeHash := crypto.HashMessage(input.Challenge)
	if !crypto.VerifySignature(pubKey, challengeHash, input.Signature) {
		return nil, ErrSignatureInvalid
	}

	// Update last login
	account.UpdateLastLogin()
	if err := uc.accountRepo.Update(ctx, account); err != nil {
		return nil, err
	}

	// Generate tokens
	accessToken, err := uc.tokenService.GenerateAccessToken(account.ID.String(), account.Username)
	if err != nil {
		return nil, err
	}

	refreshToken, err := uc.tokenService.GenerateRefreshToken(account.ID.String())
	if err != nil {
		return nil, err
	}

	// Publish login event
	if uc.eventPublisher != nil {
		_ = uc.eventPublisher.Publish(ctx, ports.AccountEvent{
			Type:      ports.EventTypeAccountLogin,
			AccountID: account.ID.String(),
			Data: map[string]interface{}{
				"username":  account.Username,
				"timestamp": time.Now().UTC(),
			},
		})
	}

	return &ports.LoginOutput{
		Account:      account,
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
	}, nil
}

// RefreshTokenInput represents input for refreshing tokens
type RefreshTokenInput struct {
	RefreshToken string
}

// RefreshTokenOutput represents output from refreshing tokens
type RefreshTokenOutput struct {
	AccessToken  string
	RefreshToken string
}

// RefreshTokenUseCase handles token refresh
type RefreshTokenUseCase struct {
	accountRepo  repositories.AccountRepository
	tokenService ports.TokenService
}

// NewRefreshTokenUseCase creates a new RefreshTokenUseCase
func NewRefreshTokenUseCase(
	accountRepo repositories.AccountRepository,
	tokenService ports.TokenService,
) *RefreshTokenUseCase {
	return &RefreshTokenUseCase{
		accountRepo:  accountRepo,
		tokenService: tokenService,
	}
}

// Execute refreshes the access token
func (uc *RefreshTokenUseCase) Execute(ctx context.Context, input RefreshTokenInput) (*RefreshTokenOutput, error) {
	// Validate refresh token and get account ID
	accountIDStr, err := uc.tokenService.ValidateRefreshToken(input.RefreshToken)
	if err != nil {
		return nil, err
	}

	// Get account to verify it still exists and is active
	accountID, err := parseAccountID(accountIDStr)
	if err != nil {
		return nil, err
	}

	account, err := uc.accountRepo.GetByID(ctx, accountID)
	if err != nil {
		return nil, err
	}

	if !account.CanLogin() {
		return nil, entities.ErrAccountNotActive
	}

	// Generate new access token
	accessToken, err := uc.tokenService.GenerateAccessToken(account.ID.String(), account.Username)
	if err != nil {
		return nil, err
	}

	// Generate new refresh token
	refreshToken, err := uc.tokenService.GenerateRefreshToken(account.ID.String())
	if err != nil {
		return nil, err
	}

	return &RefreshTokenOutput{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
	}, nil
}

// GenerateChallengeUseCase handles challenge generation for login
type GenerateChallengeUseCase struct {
	cacheService ports.CacheService
}

// NewGenerateChallengeUseCase creates a new GenerateChallengeUseCase
func NewGenerateChallengeUseCase(cacheService ports.CacheService) *GenerateChallengeUseCase {
	return &GenerateChallengeUseCase{
		cacheService: cacheService,
	}
}

// GenerateChallengeInput represents input for generating a challenge
type GenerateChallengeInput struct {
	Username string
}

// GenerateChallengeOutput represents output from generating a challenge
type GenerateChallengeOutput struct {
	Challenge   []byte
	ChallengeID string
	ExpiresAt   time.Time
}

// Execute generates a challenge for login
func (uc *GenerateChallengeUseCase) Execute(ctx context.Context, input GenerateChallengeInput) (*GenerateChallengeOutput, error) {
	// Generate random challenge
	challenge, err := crypto.GenerateRandomBytes(32)
	if err != nil {
		return nil, err
	}

	// Generate challenge ID
	challengeID, err := crypto.GenerateRandomBytes(16)
	if err != nil {
		return nil, err
	}

	challengeIDStr := hex.EncodeToString(challengeID)
	expiresAt := time.Now().UTC().Add(5 * time.Minute)

	// Store challenge in cache
	cacheKey := "login_challenge:" + challengeIDStr
	if uc.cacheService != nil {
		_ = uc.cacheService.Set(ctx, cacheKey, map[string]interface{}{
			"username":  input.Username,
			"challenge": hex.EncodeToString(challenge),
			"expiresAt": expiresAt,
		}, 300) // 5 minutes TTL
	}

	return &GenerateChallengeOutput{
		Challenge:   challenge,
		ChallengeID: challengeIDStr,
		ExpiresAt:   expiresAt,
	}, nil
}

// helper function to parse account ID
func parseAccountID(s string) (value_objects.AccountID, error) {
	return value_objects.AccountIDFromString(s)
}
