package service

import (
	"context"
	"math/rand"
	"time"

	smscontract "git.gocasts.ir/ebhomengo/niki/contract/sms"
	"git.gocasts.ir/ebhomengo/niki/domain/account/entity"
	errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
	richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
)

type Config struct {
	LengthOfOtpCode int           `koanf:"length_of_otp_code"`
	OtpChars        string        `koanf:"otp_chars"`
	OtpExpireTime   time.Duration `koanf:"otp_expire_time"`
}

type RepositoryOtp interface {
	IsExistPhoneNumber(ctx context.Context, phoneNumber string) (bool, error)
	SaveCodeWithPhoneNumber(ctx context.Context, phoneNumber string, code string, expireTime time.Duration) error
	GetCodeByPhoneNumber(ctx context.Context, phoneNumber string) (string, error)
	DeleteCodeByPhoneNumber(ctx context.Context, PhoneNumber string) (bool, error)
}

type Repository interface {
	IsExistDriverByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Driver, error)
	CreateDriver(ctx context.Context, driver entity.Driver) (entity.Driver, error)
}

type Service struct {
	config        Config
	repositoryOtp RepositoryOtp
	repository    Repository
	smsContract   smscontract.SmsAdapter
}

func NewService(cfg Config, repositoryOtp RepositoryOtp, repository Repository, smsContract smscontract.SmsAdapter) Service {
	return Service{
		config:        cfg,
		repositoryOtp: repositoryOtp,
		repository:    repository,
		smsContract:   smsContract,
	}
}

func (s Service) SendOTP(ctx context.Context, phoneNumber string) error {
	const op = "accountService.SendOTP"

	isExist, iErr := s.repositoryOtp.IsExistPhoneNumber(ctx, phoneNumber)
	if iErr != nil {
		return richerror.New(op).WithErr(iErr).WithKind(richerror.KindUnexpected)
	}

	if isExist {
		return richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeExist).WithKind(richerror.KindForbidden)
	}

	newCode := s.generateVerificationCode()
	sErr := s.repositoryOtp.SaveCodeWithPhoneNumber(ctx, phoneNumber, newCode, s.config.OtpExpireTime)
	if sErr != nil {
		return richerror.New(op).WithErr(sErr).WithKind(richerror.KindUnexpected)
	}

	go s.smsContract.Send(phoneNumber, newCode)

	return nil
}

func (s Service) LoginOrRegisterDriver(ctx context.Context, phoneNumber string, verifyCode string) (entity.Driver, error) {
	const op = "accountService.LoginOrRegisterDriver"

	code, gErr := s.repositoryOtp.GetCodeByPhoneNumber(ctx, phoneNumber)
	if gErr != nil {
		return entity.Driver{}, richerror.New(op).WithErr(gErr).WithKind(richerror.KindUnexpected)
	}

	if code == "" || code != verifyCode {
		return entity.Driver{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeIsNotValid).WithKind(richerror.KindForbidden)
	}

	_, dErr := s.repositoryOtp.DeleteCodeByPhoneNumber(ctx, phoneNumber)
	if dErr != nil {
		return entity.Driver{}, richerror.New(op).WithErr(dErr).WithKind(richerror.KindUnexpected)
	}

	isExist, driver, eErr := s.repository.IsExistDriverByPhoneNumber(ctx, phoneNumber)
	if eErr != nil {
		return entity.Driver{}, richerror.New(op).WithErr(eErr).WithKind(richerror.KindUnexpected)
	}

	if !isExist {
		newDriver, cErr := s.repository.CreateDriver(ctx, entity.Driver{
			PhoneNumber: phoneNumber,
		})
		if cErr != nil {
			return entity.Driver{}, richerror.New(op).WithErr(cErr).WithKind(richerror.KindUnexpected)
		}

		driver = newDriver
	}

	return driver, nil

}

func (s Service) generateVerificationCode() string {
	result := make([]byte, s.config.LengthOfOtpCode)
	for i := 0; i < s.config.LengthOfOtpCode; i++ {
		result[i] = s.config.OtpChars[rand.Intn(len(s.config.OtpChars))]
	}

	return string(result)
}
