package authcode

import (
	"context"
	"fmt"

	"github.com/int128/kubelogin/pkg/infrastructure/logger"
	"github.com/int128/kubelogin/pkg/infrastructure/reader"
	"github.com/int128/kubelogin/pkg/oidc"
	"github.com/int128/kubelogin/pkg/oidc/client"
	"github.com/int128/kubelogin/pkg/pkce"
)

const keyboardPrompt = "Enter code: "

type KeyboardOption struct {
	AuthRequestExtraParams map[string]string
	RedirectURL            string
}

// Keyboard provides the authorization code flow with keyboard interactive.
type Keyboard struct {
	Reader reader.Interface
	Logger logger.Interface
}

func (u *Keyboard) Do(ctx context.Context, o *KeyboardOption, oidcClient client.Interface) (*oidc.TokenSet, error) {
	u.Logger.V(1).Infof("starting the authorization code flow with keyboard interactive")
	state, err := oidc.NewState()
	if err != nil {
		return nil, fmt.Errorf("could not generate a state: %w", err)
	}
	nonce, err := oidc.NewNonce()
	if err != nil {
		return nil, fmt.Errorf("could not generate a nonce: %w", err)
	}
	p, err := pkce.New(oidcClient.SupportedPKCEMethods())
	if err != nil {
		return nil, fmt.Errorf("could not generate PKCE parameters: %w", err)
	}

	authCodeURL := oidcClient.GetAuthCodeURL(client.AuthCodeURLInput{
		State:                  state,
		Nonce:                  nonce,
		PKCEParams:             p,
		RedirectURI:            o.RedirectURL,
		AuthRequestExtraParams: o.AuthRequestExtraParams,
	})
	u.Logger.Printf("Please visit the following URL in your browser: %s", authCodeURL)
	code, err := u.Reader.ReadString(keyboardPrompt)
	if err != nil {
		return nil, fmt.Errorf("could not read an authorization code: %w", err)
	}

	u.Logger.V(1).Infof("exchanging the code and token")
	tokenSet, err := oidcClient.ExchangeAuthCode(ctx, client.ExchangeAuthCodeInput{
		Code:        code,
		PKCEParams:  p,
		Nonce:       nonce,
		RedirectURI: o.RedirectURL,
	})
	if err != nil {
		return nil, fmt.Errorf("could not exchange the authorization code: %w", err)
	}
	u.Logger.V(1).Infof("finished the authorization code flow with keyboard interactive")
	return tokenSet, nil
}
