/*
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>

Scotty is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later version.

Scotty is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
Scotty. If not, see <https://www.gnu.org/licenses/>.
*/

package cli

import (
	"fmt"
	"os"

	"github.com/cli/browser"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"go.uploadedlobster.com/scotty/internal/auth"
	"go.uploadedlobster.com/scotty/internal/backends"
	"go.uploadedlobster.com/scotty/internal/config"
	"go.uploadedlobster.com/scotty/internal/i18n"
	"go.uploadedlobster.com/scotty/internal/storage"
	"golang.org/x/oauth2"
)

func AuthenticationFlow(service config.ServiceConfig, backend auth.OAuth2Authenticator) {
	redirectURL, err := backends.BuildRedirectURL(viper.GetViper(), backend.Name())
	cobra.CheckErr(err)

	// The backend must provide an authentication strategy
	strategy := backend.OAuth2Strategy(redirectURL)

	// use PKCE to protect against CSRF attacks
	// https://www.ietf.org/archive/id/draft-ietf-oauth-security-topics-22.html#name-countermeasures-6
	verifier := oauth2.GenerateVerifier()

	state := auth.RandomState()
	// Redirect user to consent page to ask for permission specified scopes.
	authUrl := strategy.AuthCodeURL(verifier, state)

	// Start an HTTP server to listen for the response
	responseChan := make(chan auth.CodeResponse)
	auth.RunOauth2CallbackServer(*redirectURL, authUrl.Param, responseChan)

	// Open the URL
	fmt.Println(i18n.Tr("Visit the URL for authorization: %v", authUrl.Url))
	err = browser.OpenURL(authUrl.Url)
	cobra.CheckErr(err)

	// Retrieve the code from the authentication callback
	code := <-responseChan
	if code.State != authUrl.State {
		cobra.CompErrorln(i18n.Tr("Error: OAuth state mismatch"))
		os.Exit(1)
	}

	// Exchange the code for the authentication token
	tok, err := strategy.ExchangeToken(code, verifier)
	cobra.CheckErr(err)

	// Store the retrieved token in the database
	db, err := storage.New(config.DatabasePath())
	cobra.CheckErr(err)

	err = db.SetOAuth2Token(service.Name, tok)
	cobra.CheckErr(err)

	fmt.Println(i18n.Tr("Access token received, you can use %v now.\n", service.Name))
}