package account

import (
	"context"
	"fmt"
	"log"
	"math"

	"go.uber.org/zap"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/errors"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/internal/models"
)

type accountRepository interface {
	FindByUserID(ctx context.Context, id string) (*models.Account, errors.Error)
	FindMany(ctx context.Context, page, limit int64) ([]models.Account, errors.Error)
	Insert(ctx context.Context, account *models.Account) (*models.Account, errors.Error)
	Remove(ctx context.Context, id string) (*models.Account, errors.Error)
	Delete(ctx context.Context, id string) (*models.Account, errors.Error)
	CountAll(ctx context.Context) (int64, errors.Error)
	SetStatus(ctx context.Context, userID string, status models.AccountStatus) (*models.Account, errors.Error)
	UpdateName(ctx context.Context, userID string, name *models.Name) (*models.Account, errors.Error)
}

const defaultCurrency = "RUB"

type authClient interface {
	GetUser(ctx context.Context, userID string) (*models.User, errors.Error)
}

type Deps struct {
	Logger     *zap.Logger
	Repository accountRepository
	AuthClient authClient
}

type Service struct {
	logger     *zap.Logger
	repository accountRepository
	authClient authClient
}

func New(deps Deps) *Service {
	if deps.Logger == nil {
		log.Panicln("logger is nil on <New (account service)>")
	}

	if deps.Repository == nil {
		log.Panicln("repository is nil on <New (account service)>")
	}

	if deps.AuthClient == nil {
		log.Panicln("auth client is nil on <New (account service)>")
	}

	return &Service{
		logger:     deps.Logger,
		repository: deps.Repository,
		authClient: deps.AuthClient,
	}
}

func (receiver *Service) GetAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error) {
	account, err := receiver.repository.FindByUserID(ctx, userID)
	if err != nil {
		receiver.logger.Error("failed to get account by id on <GetAccountByUserID> of <AccountService>",
			zap.Error(err),
			zap.String("userID", userID),
		)

		return nil, err
	}

	return account, nil
}

func (receiver *Service) GetAccountsList(ctx context.Context, pagination *models.Pagination) (*models.PaginationResponse[models.Account], errors.Error) {
	if pagination == nil {
		return nil, errors.New(
			fmt.Errorf("pagination is nil on <GetAccountsList> of <AccountService>: %w", errors.ErrInternalError),
			errors.ErrInternalError,
		)
	}

	count, err := receiver.repository.CountAll(ctx)
	if err != nil {
		receiver.logger.Error("failed to count accounts on <GetAccountsList> of <AccountService>",
			zap.Error(err),
		)

		return nil, err
	}

	if count == 0 {
		return &models.PaginationResponse[models.Account]{TotalPages: 0, Records: []models.Account{}}, nil
	}

	totalPages := int64(math.Ceil(float64(count) / float64(pagination.Limit)))

	accounts, err := receiver.repository.FindMany(ctx, pagination.Page, pagination.Limit)
	if err != nil {
		receiver.logger.Error("failed to get accounts list on <GetAccountsList> of <AccountService>",
			zap.Error(err),
			zap.Int64("page", pagination.Page),
			zap.Int64("limit", pagination.Limit),
		)

		return nil, err
	}

	return &models.PaginationResponse[models.Account]{
		TotalPages: totalPages,
		Records:    accounts,
	}, nil
}

func (receiver *Service) CreateAccount(ctx context.Context, account *models.Account) (*models.Account, errors.Error) {
	findedAccount, err := receiver.GetAccountByUserID(ctx, account.UserID)
	if err != nil && err.Type() != errors.ErrNotFound {
		receiver.logger.Error("failed to find account on <CreateAccount> of <AccountService>",
			zap.Error(err),
		)

		return nil, err
	}

	if findedAccount != nil {
		return nil, errors.New(
			fmt.Errorf("failed to create account with <%s> on <CreateAccount> of <AccountService>: %w",
				account.UserID,
				errors.ErrConflict,
			),
			errors.ErrConflict,
		)
	}

	createdAccount, err := receiver.repository.Insert(ctx, account)
	if err != nil {
		receiver.logger.Error("failed to create account on <CreateAccount> of <AccountService>",
			zap.Error(err),
			zap.Any("account", account),
		)

		return nil, err
	}

	return createdAccount, nil
}

func (receiver *Service) CreateAccountByUserID(ctx context.Context, userID string) (*models.Account, errors.Error) {
	account, err := receiver.GetAccountByUserID(ctx, userID)
	if err != nil && err.Type() != errors.ErrNotFound {
		receiver.logger.Error("failed to find account on <CreateAccountByUserID> of <AccountService>",
			zap.Error(err),
		)

		return nil, err
	}

	if account != nil {
		return nil, errors.New(
			fmt.Errorf("failed to create account with <%s> on <CreateAccountByUserID> of <AccountService>: %w",
				userID,
				errors.ErrConflict,
			),
			errors.ErrConflict,
		)
	}

	user, err := receiver.authClient.GetUser(ctx, userID)
	if err != nil {
		receiver.logger.Error("failed to get user on <CreateAccountByUserID> of <AccountService>",
			zap.Error(err),
			zap.String("userID", userID),
		)

		return nil, err
	}

	createdAccount, err := receiver.repository.Insert(ctx, &models.Account{UserID: user.ID, Wallet: models.Wallet{Currency: defaultCurrency}})
	if err != nil {
		receiver.logger.Error("failed to create account on <CreateAccountByUserID> of <AccountService>",
			zap.Error(err),
			zap.String("userID", userID),
		)

		return nil, err
	}

	return createdAccount, nil
}

func (receiver *Service) RemoveAccount(ctx context.Context, userID string) (*models.Account, errors.Error) {
	account, err := receiver.repository.Remove(ctx, userID)
	if err != nil {
		receiver.logger.Error("failed to remove account on <RemoveAccount> of <AccountService>",
			zap.Error(err),
			zap.String("userID", userID),
		)

		return nil, err
	}

	return account, nil
}

func (receiver *Service) DeleteAccount(ctx context.Context, userID string) (*models.Account, errors.Error) {
	account, err := receiver.repository.Delete(ctx, userID)
	if err != nil {
		receiver.logger.Error("failed to delete account on <DeleteAccount> of <AccountService>",
			zap.Error(err),
			zap.String("userID", userID),
		)

		return nil, err
	}

	return account, nil
}

func (receiver *Service) SetVerificationStatus(ctx context.Context, userID string, status models.AccountStatus) (*models.Account, errors.Error) {
	account, err := receiver.repository.SetStatus(ctx, userID, status)
	if err != nil {
		receiver.logger.Error("failed to set status on <SetVerificationStatus> of <AccountService>", zap.Error(err))
		return nil, err
	}

	return account, nil
}

func (receiver *Service) UpdateAccountName(ctx context.Context, userID string, name *models.Name) (*models.Account, errors.Error) {
	account, err := receiver.repository.UpdateName(ctx, userID, name)
	if err != nil {
		receiver.logger.Error("failed to update account name on <UpdateAccountName> of <AccountService>", zap.Error(err))
		return nil, err
	}

	return account, nil
}
