package services

import (
	"context"

	"github.com/google/uuid"
	"github.com/rwadurian/mpc-system/pkg/crypto"
	"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"
)

// AccountDomainService provides domain logic for accounts
type AccountDomainService struct {
	accountRepo  repositories.AccountRepository
	shareRepo    repositories.AccountShareRepository
	recoveryRepo repositories.RecoverySessionRepository
}

// NewAccountDomainService creates a new AccountDomainService
func NewAccountDomainService(
	accountRepo repositories.AccountRepository,
	shareRepo repositories.AccountShareRepository,
	recoveryRepo repositories.RecoverySessionRepository,
) *AccountDomainService {
	return &AccountDomainService{
		accountRepo:  accountRepo,
		shareRepo:    shareRepo,
		recoveryRepo: recoveryRepo,
	}
}

// CreateAccountInput represents input for creating an account
type CreateAccountInput struct {
	Username        string
	Email           string
	Phone           *string
	PublicKey       []byte
	KeygenSessionID uuid.UUID
	ThresholdN      int
	ThresholdT      int
	Shares          []ShareInfo
}

// ShareInfo represents information about a key share
type ShareInfo struct {
	ShareType  value_objects.ShareType
	PartyID    string
	PartyIndex int
	DeviceType *string
	DeviceID   *string
}

// CreateAccount creates a new account with shares
func (s *AccountDomainService) CreateAccount(ctx context.Context, input CreateAccountInput) (*entities.Account, error) {
	// Check username uniqueness
	exists, err := s.accountRepo.ExistsByUsername(ctx, input.Username)
	if err != nil {
		return nil, err
	}
	if exists {
		return nil, entities.ErrDuplicateUsername
	}

	// Check email uniqueness
	exists, err = s.accountRepo.ExistsByEmail(ctx, input.Email)
	if err != nil {
		return nil, err
	}
	if exists {
		return nil, entities.ErrDuplicateEmail
	}

	// Create account
	account := entities.NewAccount(
		input.Username,
		input.Email,
		input.PublicKey,
		input.KeygenSessionID,
		input.ThresholdN,
		input.ThresholdT,
	)

	if input.Phone != nil {
		account.SetPhone(*input.Phone)
	}

	// Validate account
	if err := account.Validate(); err != nil {
		return nil, err
	}

	// Create account in repository
	if err := s.accountRepo.Create(ctx, account); err != nil {
		return nil, err
	}

	// Create shares
	for _, shareInfo := range input.Shares {
		share := entities.NewAccountShare(
			account.ID,
			shareInfo.ShareType,
			shareInfo.PartyID,
			shareInfo.PartyIndex,
		)

		if shareInfo.DeviceType != nil && shareInfo.DeviceID != nil {
			share.SetDeviceInfo(*shareInfo.DeviceType, *shareInfo.DeviceID)
		}

		if err := share.Validate(); err != nil {
			return nil, err
		}

		if err := s.shareRepo.Create(ctx, share); err != nil {
			return nil, err
		}
	}

	return account, nil
}

// VerifySignature verifies a signature against an account's public key
func (s *AccountDomainService) VerifySignature(ctx context.Context, accountID value_objects.AccountID, message, signature []byte) (bool, error) {
	account, err := s.accountRepo.GetByID(ctx, accountID)
	if err != nil {
		return false, err
	}

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

	// Verify signature
	valid := crypto.VerifySignature(pubKey, message, signature)
	return valid, nil
}

// InitiateRecovery initiates account recovery
func (s *AccountDomainService) InitiateRecovery(ctx context.Context, accountID value_objects.AccountID, recoveryType value_objects.RecoveryType, oldShareType *value_objects.ShareType) (*entities.RecoverySession, error) {
	// Get account
	account, err := s.accountRepo.GetByID(ctx, accountID)
	if err != nil {
		return nil, err
	}

	// Check if recovery can be initiated
	if err := account.StartRecovery(); err != nil {
		return nil, err
	}

	// Update account status
	if err := s.accountRepo.Update(ctx, account); err != nil {
		return nil, err
	}

	// Create recovery session
	recoverySession := entities.NewRecoverySession(accountID, recoveryType)
	if oldShareType != nil {
		recoverySession.SetOldShareType(*oldShareType)
	}

	if err := recoverySession.Validate(); err != nil {
		return nil, err
	}

	if err := s.recoveryRepo.Create(ctx, recoverySession); err != nil {
		return nil, err
	}

	return recoverySession, nil
}

// CompleteRecovery completes the recovery process
func (s *AccountDomainService) CompleteRecovery(ctx context.Context, recoverySessionID string, newPublicKey []byte, newKeygenSessionID uuid.UUID, newShares []ShareInfo) error {
	// Get recovery session
	recovery, err := s.recoveryRepo.GetByID(ctx, recoverySessionID)
	if err != nil {
		return err
	}

	// Start keygen if still in requested state (transitions to in_progress)
	if recovery.Status == value_objects.RecoveryStatusRequested {
		if err := recovery.StartKeygen(newKeygenSessionID); err != nil {
			return err
		}
	}

	// Complete recovery session
	if err := recovery.Complete(); err != nil {
		return err
	}

	// Get account
	account, err := s.accountRepo.GetByID(ctx, recovery.AccountID)
	if err != nil {
		return err
	}

	// Complete account recovery
	account.CompleteRecovery(newPublicKey, newKeygenSessionID)

	// Deactivate old shares
	if err := s.shareRepo.DeactivateByAccountID(ctx, account.ID); err != nil {
		return err
	}

	// Create new shares
	for _, shareInfo := range newShares {
		share := entities.NewAccountShare(
			account.ID,
			shareInfo.ShareType,
			shareInfo.PartyID,
			shareInfo.PartyIndex,
		)

		if shareInfo.DeviceType != nil && shareInfo.DeviceID != nil {
			share.SetDeviceInfo(*shareInfo.DeviceType, *shareInfo.DeviceID)
		}

		if err := s.shareRepo.Create(ctx, share); err != nil {
			return err
		}
	}

	// Update account
	if err := s.accountRepo.Update(ctx, account); err != nil {
		return err
	}

	// Update recovery session
	if err := s.recoveryRepo.Update(ctx, recovery); err != nil {
		return err
	}

	return nil
}

// GetActiveShares returns active shares for an account
func (s *AccountDomainService) GetActiveShares(ctx context.Context, accountID value_objects.AccountID) ([]*entities.AccountShare, error) {
	return s.shareRepo.GetActiveByAccountID(ctx, accountID)
}

// CanAccountSign checks if an account has enough active shares to sign
func (s *AccountDomainService) CanAccountSign(ctx context.Context, accountID value_objects.AccountID) (bool, error) {
	account, err := s.accountRepo.GetByID(ctx, accountID)
	if err != nil {
		return false, err
	}

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

	shares, err := s.shareRepo.GetActiveByAccountID(ctx, accountID)
	if err != nil {
		return false, err
	}

	// Count active shares
	activeCount := 0
	for _, share := range shares {
		if share.IsActive {
			activeCount++
		}
	}

	// Check if we have enough shares for threshold
	return activeCount >= account.ThresholdT, nil
}
