package auth

import (
	
	
	
	
	
	

	

	
	
)

// NewFlow initializes new authentication flow.
func ( UserAuthenticator,  SendCodeOptions) Flow {
	return Flow{
		Auth:    ,
		Options: ,
	}
}

// Flow simplifies boilerplate for authentication flow.
type Flow struct {
	Auth    UserAuthenticator
	Options SendCodeOptions
}

func ( Flow) ( context.Context,  FlowClient, ,  string,  *SignUpRequired) error {
	if  := .Auth.AcceptTermsOfService(, .TermsOfService);  != nil {
		return errors.Wrap(, "confirm TOS")
	}
	,  := .Auth.SignUp()
	if  != nil {
		return errors.Wrap(, "sign up info not provided")
	}
	if ,  := .SignUp(, SignUp{
		PhoneNumber:   ,
		PhoneCodeHash: ,
		FirstName:     .FirstName,
		LastName:      .LastName,
	});  != nil {
		return errors.Wrap(, "sign up")
	}
	return nil
}

// Run starts authentication flow on client.
func ( Flow) ( context.Context,  FlowClient) error {
	if .Auth == nil {
		return errors.New("no UserAuthenticator provided")
	}

	,  := .Auth.Phone()
	if  != nil {
		return errors.Wrap(, "get phone")
	}

	,  := .SendCode(, , .Options)
	if  != nil {
		return errors.Wrap(, "send code")
	}
	switch s := .(type) {
	case *tg.AuthSentCode:
		 := .PhoneCodeHash
		,  := .Auth.Code(, )
		if  != nil {
			return errors.Wrap(, "get code")
		}

		,  := .SignIn(, , , )
		if errors.Is(, ErrPasswordAuthNeeded) {
			,  := .Auth.Password()
			if  != nil {
				return errors.Wrap(, "get password")
			}
			if ,  := .Password(, );  != nil {
				return errors.Wrap(, "sign in with password")
			}
			return nil
		}
		var  *SignUpRequired
		if errors.As(, &) {
			return .handleSignUp(, , , , )
		}

		if  != nil {
			return errors.Wrap(, "sign in")
		}

		return nil
	case *tg.AuthSentCodeSuccess:
		switch a := .Authorization.(type) {
		case *tg.AuthAuthorization:
			// Looks that we are already authorized.
			return nil
		case *tg.AuthAuthorizationSignUpRequired:
			if  := .handleSignUp(, , , "", &SignUpRequired{
				TermsOfService: .TermsOfService,
			});  != nil {
				// TODO: not sure that blank hash will work here
				return errors.Wrap(, "sign up after auth sent code success")
			}
			return nil
		default:
			return errors.Errorf("unexpected authorization type: %T", )
		}
	default:
		return errors.Errorf("unexpected sent code type: %T", )
	}
}

// FlowClient abstracts telegram client for Flow.
type FlowClient interface {
	SignIn(ctx context.Context, phone, code, codeHash string) (*tg.AuthAuthorization, error)
	SendCode(ctx context.Context, phone string, options SendCodeOptions) (tg.AuthSentCodeClass, error)
	Password(ctx context.Context, password string) (*tg.AuthAuthorization, error)
	SignUp(ctx context.Context, s SignUp) (*tg.AuthAuthorization, error)
}

// CodeAuthenticator asks user for received authentication code.
type CodeAuthenticator interface {
	Code(ctx context.Context, sentCode *tg.AuthSentCode) (string, error)
}

// CodeAuthenticatorFunc is functional wrapper for CodeAuthenticator.
type CodeAuthenticatorFunc func(ctx context.Context, sentCode *tg.AuthSentCode) (string, error)

// Code implements CodeAuthenticator interface.
func ( CodeAuthenticatorFunc) ( context.Context,  *tg.AuthSentCode) (string, error) {
	return (, )
}

// UserInfo represents user info required for sign up.
type UserInfo struct {
	FirstName string
	LastName  string
}

// UserAuthenticator asks user for phone, password and received authentication code.
type UserAuthenticator interface {
	Phone(ctx context.Context) (string, error)
	Password(ctx context.Context) (string, error)
	AcceptTermsOfService(ctx context.Context, tos tg.HelpTermsOfService) error
	SignUp(ctx context.Context) (UserInfo, error)
	CodeAuthenticator
}

type noSignUp struct{}

func ( noSignUp) ( context.Context) (UserInfo, error) {
	return UserInfo{}, errors.New("not implemented")
}

func ( noSignUp) ( context.Context,  tg.HelpTermsOfService) error {
	return &SignUpRequired{TermsOfService: }
}

type constantAuth struct {
	phone, password string
	CodeAuthenticator
	noSignUp
}

func ( constantAuth) ( context.Context) (string, error) {
	return .phone, nil
}

func ( constantAuth) ( context.Context) (string, error) {
	return .password, nil
}

// Constant creates UserAuthenticator with constant phone and password.
func (,  string,  CodeAuthenticator) UserAuthenticator {
	return constantAuth{
		phone:             ,
		password:          ,
		CodeAuthenticator: ,
	}
}

type envAuth struct {
	prefix string
	CodeAuthenticator
	noSignUp
}

func ( envAuth) ( string) (string, error) {
	 := .prefix + 
	,  := os.LookupEnv()
	if ! {
		return "", errors.Errorf("environment variable %q not set", )
	}
	return , nil
}

func ( envAuth) ( context.Context) (string, error) {
	return .lookup("PHONE")
}

func ( envAuth) ( context.Context) (string, error) {
	,  := .lookup("PASSWORD")
	if  != nil {
		return "", ErrPasswordNotProvided
	}
	return , nil
}

// Env creates UserAuthenticator which gets phone and password from environment variables.
func ( string,  CodeAuthenticator) UserAuthenticator {
	return envAuth{
		prefix:            ,
		CodeAuthenticator: ,
		noSignUp:          noSignUp{},
	}
}

// ErrPasswordNotProvided means that password requested by Telegram,
// but not provided by user.
var ErrPasswordNotProvided = errors.New("password requested but not provided")

type codeOnlyAuth struct {
	phone string
	CodeAuthenticator
	noSignUp
}

func ( codeOnlyAuth) ( context.Context) (string, error) {
	return .phone, nil
}

func ( codeOnlyAuth) ( context.Context) (string, error) {
	return "", ErrPasswordNotProvided
}

// CodeOnly creates UserAuthenticator with constant phone and no password.
func ( string,  CodeAuthenticator) UserAuthenticator {
	return codeOnlyAuth{
		phone:             ,
		CodeAuthenticator: ,
	}
}

type testAuth struct {
	dc    int
	phone string
}

func ( testAuth) ( context.Context) (string, error)    { return .phone, nil }
func ( testAuth) ( context.Context) (string, error) { return "", ErrPasswordNotProvided }
func ( testAuth) ( context.Context,  *tg.AuthSentCode) (string, error) {
	type  interface {
		() int
	}

	 := 5
	if  != nil {
		,  := .Type.()
		if ! {
			return "", errors.Errorf("unexpected type: %T", .Type)
		}
		 = .()
	}

	return strings.Repeat(strconv.Itoa(.dc), ), nil
}

func ( testAuth) ( context.Context,  tg.HelpTermsOfService) error {
	return nil
}

func ( testAuth) ( context.Context) (UserInfo, error) {
	return UserInfo{
		FirstName: "Test",
		LastName:  "User",
	}, nil
}

// Test returns UserAuthenticator that authenticates via testing credentials.
//
// Can be used only with testing server. Will perform sign up if test user is
// not registered.
func ( io.Reader,  int) UserAuthenticator {
	// 99966XYYYY, X = dc_id, Y = random numbers, code = X repeat 6.
	// The n value is from 0000 to 9999.
	,  := crypto.RandInt64n(, 1000)
	if  != nil {
		panic()
	}
	 := fmt.Sprintf("99966%d%04d", , )

	return TestUser(, )
}

// TestUser returns UserAuthenticator that authenticates via testing credentials.
// Uses given phone to sign in/sign up.
//
// Can be used only with testing server. Will perform sign up if test user is
// not registered.
func ( string,  int) UserAuthenticator {
	return testAuth{
		dc:    ,
		phone: ,
	}
}