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

This file is part of Scotty.

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 cmd

import (
	"fmt"
	"os"

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

// authCmd represents the auth command
var authCmd = &cobra.Command{
	Use:   "auth",
	Short: "Authenticate with a backend",
	Long:  `For backends requiring authentication this command can be used to authenticate.`,
	Run: func(cmd *cobra.Command, args []string) {
		serviceName, serviceConfig := getConfigFromFlag(cmd, "service")
		backend, err := backends.ResolveBackend[models.OAuth2Authenticator](serviceConfig)
		cobra.CheckErr(err)

		redirectURL, err := backends.BuildRedirectURL(viper.GetViper(), backend.Name())
		cobra.CheckErr(err)

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

		// 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 := "somestate" // FIXME: Should be a random string
		// Redirect user to consent page to ask for permission specified scopes.
		url := strategy.AuthCodeURL(verifier, state)
		fmt.Printf("Visit the URL for the auth dialog: %v\n", url)
		err = browser.OpenURL(url)
		cobra.CheckErr(err)

		// Retrieve the code from the authentication callback
		code := <-responseChan
		if code.State != state {
			cobra.CompErrorln("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(viper.GetString("database"))
		cobra.CheckErr(err)

		err = db.SetOAuth2Token(serviceName, tok)
		cobra.CheckErr(err)

		fmt.Printf("Access token received, you can use %v now.\n\n", serviceName)
	},
}

func init() {
	rootCmd.AddCommand(authCmd)

	authCmd.Flags().StringP("service", "s", "", "Service configuration (required)")
	authCmd.MarkFlagRequired("service")
}