/*
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 (
	"errors"
	"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/cli"
	"go.uploadedlobster.com/scotty/internal/config"
	"go.uploadedlobster.com/scotty/internal/models"
	"go.uploadedlobster.com/scotty/internal/storage"
	"golang.org/x/oauth2"
)

var serviceAuthCmd = &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) {
		serviceConfig := cli.GetServiceConfigFromFlag(cmd, "service")
		if serviceConfig == nil {
			cobra.CheckErr(errors.New("failed loading service configuration"))
		}
		backend, err := backends.ResolveBackend[models.OAuth2Authenticator](serviceConfig)
		cobra.CheckErr(err)

		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.Printf("Visit the URL for the auth dialog: %v\n", 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("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(serviceConfig.Name, tok)
		cobra.CheckErr(err)

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

func init() {
	serviceCmd.AddCommand(serviceAuthCmd)

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