package use_cases

import (
	"context"

	"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/services"
	"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
)

// CreateAccountUseCase handles account creation
type CreateAccountUseCase struct {
	accountRepo    repositories.AccountRepository
	shareRepo      repositories.AccountShareRepository
	domainService  *services.AccountDomainService
	eventPublisher ports.EventPublisher
}

// NewCreateAccountUseCase creates a new CreateAccountUseCase
func NewCreateAccountUseCase(
	accountRepo repositories.AccountRepository,
	shareRepo repositories.AccountShareRepository,
	domainService *services.AccountDomainService,
	eventPublisher ports.EventPublisher,
) *CreateAccountUseCase {
	return &CreateAccountUseCase{
		accountRepo:    accountRepo,
		shareRepo:      shareRepo,
		domainService:  domainService,
		eventPublisher: eventPublisher,
	}
}

// Execute creates a new account
func (uc *CreateAccountUseCase) Execute(ctx context.Context, input ports.CreateAccountInput) (*ports.CreateAccountOutput, error) {
	// Convert shares input
	shares := make([]services.ShareInfo, len(input.Shares))
	for i, s := range input.Shares {
		shares[i] = services.ShareInfo{
			ShareType:  s.ShareType,
			PartyID:    s.PartyID,
			PartyIndex: s.PartyIndex,
			DeviceType: s.DeviceType,
			DeviceID:   s.DeviceID,
		}
	}

	// Create account using domain service
	account, err := uc.domainService.CreateAccount(ctx, services.CreateAccountInput{
		Username:        input.Username,
		Email:           input.Email,
		Phone:           input.Phone,
		PublicKey:       input.PublicKey,
		KeygenSessionID: input.KeygenSessionID,
		ThresholdN:      input.ThresholdN,
		ThresholdT:      input.ThresholdT,
		Shares:          shares,
	})
	if err != nil {
		return nil, err
	}

	// Get created shares
	accountShares, err := uc.shareRepo.GetByAccountID(ctx, account.ID)
	if err != nil {
		return nil, err
	}

	// Publish event
	if uc.eventPublisher != nil {
		_ = uc.eventPublisher.Publish(ctx, ports.AccountEvent{
			Type:      ports.EventTypeAccountCreated,
			AccountID: account.ID.String(),
			Data: map[string]interface{}{
				"username":   account.Username,
				"email":      account.Email,
				"thresholdN": account.ThresholdN,
				"thresholdT": account.ThresholdT,
			},
		})
	}

	return &ports.CreateAccountOutput{
		Account: account,
		Shares:  accountShares,
	}, nil
}

// GetAccountUseCase handles getting account information
type GetAccountUseCase struct {
	accountRepo repositories.AccountRepository
	shareRepo   repositories.AccountShareRepository
}

// NewGetAccountUseCase creates a new GetAccountUseCase
func NewGetAccountUseCase(
	accountRepo repositories.AccountRepository,
	shareRepo repositories.AccountShareRepository,
) *GetAccountUseCase {
	return &GetAccountUseCase{
		accountRepo: accountRepo,
		shareRepo:   shareRepo,
	}
}

// Execute gets account information
func (uc *GetAccountUseCase) Execute(ctx context.Context, input ports.GetAccountInput) (*ports.GetAccountOutput, error) {
	var account *entities.Account
	var err error

	switch {
	case input.AccountID != nil:
		account, err = uc.accountRepo.GetByID(ctx, *input.AccountID)
	case input.Username != nil:
		account, err = uc.accountRepo.GetByUsername(ctx, *input.Username)
	case input.Email != nil:
		account, err = uc.accountRepo.GetByEmail(ctx, *input.Email)
	default:
		return nil, entities.ErrAccountNotFound
	}

	if err != nil {
		return nil, err
	}

	// Get shares
	shares, err := uc.shareRepo.GetActiveByAccountID(ctx, account.ID)
	if err != nil {
		return nil, err
	}

	return &ports.GetAccountOutput{
		Account: account,
		Shares:  shares,
	}, nil
}

// UpdateAccountUseCase handles account updates
type UpdateAccountUseCase struct {
	accountRepo    repositories.AccountRepository
	eventPublisher ports.EventPublisher
}

// NewUpdateAccountUseCase creates a new UpdateAccountUseCase
func NewUpdateAccountUseCase(
	accountRepo repositories.AccountRepository,
	eventPublisher ports.EventPublisher,
) *UpdateAccountUseCase {
	return &UpdateAccountUseCase{
		accountRepo:    accountRepo,
		eventPublisher: eventPublisher,
	}
}

// Execute updates an account
func (uc *UpdateAccountUseCase) Execute(ctx context.Context, input ports.UpdateAccountInput) (*ports.UpdateAccountOutput, error) {
	account, err := uc.accountRepo.GetByID(ctx, input.AccountID)
	if err != nil {
		return nil, err
	}

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

	// Handle signing parties update
	if input.ClearSigningParties {
		account.ClearSigningParties()
	} else if len(input.SigningParties) > 0 {
		if err := account.SetSigningParties(input.SigningParties); err != nil {
			return nil, err
		}
	}

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

	// Publish event
	if uc.eventPublisher != nil {
		_ = uc.eventPublisher.Publish(ctx, ports.AccountEvent{
			Type:      ports.EventTypeAccountUpdated,
			AccountID: account.ID.String(),
			Data:      map[string]interface{}{},
		})
	}

	return &ports.UpdateAccountOutput{
		Account: account,
	}, nil
}

// DeactivateShareUseCase handles share deactivation
type DeactivateShareUseCase struct {
	accountRepo    repositories.AccountRepository
	shareRepo      repositories.AccountShareRepository
	eventPublisher ports.EventPublisher
}

// NewDeactivateShareUseCase creates a new DeactivateShareUseCase
func NewDeactivateShareUseCase(
	accountRepo repositories.AccountRepository,
	shareRepo repositories.AccountShareRepository,
	eventPublisher ports.EventPublisher,
) *DeactivateShareUseCase {
	return &DeactivateShareUseCase{
		accountRepo:    accountRepo,
		shareRepo:      shareRepo,
		eventPublisher: eventPublisher,
	}
}

// Execute deactivates a share
func (uc *DeactivateShareUseCase) Execute(ctx context.Context, input ports.DeactivateShareInput) error {
	// Verify account exists
	_, err := uc.accountRepo.GetByID(ctx, input.AccountID)
	if err != nil {
		return err
	}

	// Get share
	share, err := uc.shareRepo.GetByID(ctx, input.ShareID)
	if err != nil {
		return err
	}

	// Verify share belongs to account
	if !share.AccountID.Equals(input.AccountID) {
		return entities.ErrShareNotFound
	}

	// Deactivate share
	share.Deactivate()
	if err := uc.shareRepo.Update(ctx, share); err != nil {
		return err
	}

	// Publish event
	if uc.eventPublisher != nil {
		_ = uc.eventPublisher.Publish(ctx, ports.AccountEvent{
			Type:      ports.EventTypeShareDeactivated,
			AccountID: input.AccountID.String(),
			Data: map[string]interface{}{
				"shareId":   input.ShareID,
				"shareType": share.ShareType.String(),
			},
		})
	}

	return nil
}

// ListAccountsInput represents input for listing accounts
type ListAccountsInput struct {
	Offset int
	Limit  int
}

// ListAccountsOutput represents output from listing accounts
type ListAccountsOutput struct {
	Accounts []*entities.Account
	Total    int64
}

// ListAccountsUseCase handles listing accounts
type ListAccountsUseCase struct {
	accountRepo repositories.AccountRepository
}

// NewListAccountsUseCase creates a new ListAccountsUseCase
func NewListAccountsUseCase(accountRepo repositories.AccountRepository) *ListAccountsUseCase {
	return &ListAccountsUseCase{
		accountRepo: accountRepo,
	}
}

// Execute lists accounts with pagination
func (uc *ListAccountsUseCase) Execute(ctx context.Context, input ListAccountsInput) (*ListAccountsOutput, error) {
	if input.Limit <= 0 {
		input.Limit = 20
	}
	if input.Limit > 100 {
		input.Limit = 100
	}

	accounts, err := uc.accountRepo.List(ctx, input.Offset, input.Limit)
	if err != nil {
		return nil, err
	}

	total, err := uc.accountRepo.Count(ctx)
	if err != nil {
		return nil, err
	}

	return &ListAccountsOutput{
		Accounts: accounts,
		Total:    total,
	}, nil
}

// GetAccountSharesUseCase handles getting account shares
type GetAccountSharesUseCase struct {
	accountRepo repositories.AccountRepository
	shareRepo   repositories.AccountShareRepository
}

// NewGetAccountSharesUseCase creates a new GetAccountSharesUseCase
func NewGetAccountSharesUseCase(
	accountRepo repositories.AccountRepository,
	shareRepo repositories.AccountShareRepository,
) *GetAccountSharesUseCase {
	return &GetAccountSharesUseCase{
		accountRepo: accountRepo,
		shareRepo:   shareRepo,
	}
}

// GetAccountSharesOutput represents output from getting account shares
type GetAccountSharesOutput struct {
	Shares []*entities.AccountShare
}

// Execute gets shares for an account
func (uc *GetAccountSharesUseCase) Execute(ctx context.Context, accountID value_objects.AccountID) (*GetAccountSharesOutput, error) {
	// Verify account exists
	_, err := uc.accountRepo.GetByID(ctx, accountID)
	if err != nil {
		return nil, err
	}

	shares, err := uc.shareRepo.GetByAccountID(ctx, accountID)
	if err != nil {
		return nil, err
	}

	return &GetAccountSharesOutput{
		Shares: shares,
	}, nil
}
