package login

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/url"
	"reflect"
	"time"

	"github.com/scaleway/scaleway-cli/v2/core"
	initCommand "github.com/scaleway/scaleway-cli/v2/internal/namespaces/init"
	"github.com/scaleway/scaleway-cli/v2/internal/namespaces/login/webcallback"
	iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
	"github.com/scaleway/scaleway-sdk-go/logger"
	"github.com/scaleway/scaleway-sdk-go/scw"
	"github.com/skratchdot/open-golang/open"
)

func GetCommands() *core.Commands {
	return core.NewCommands(loginCommand())
}

type loginArgs struct {
	Port int `json:"port"`
	// PrintURL will print the account url instead of trying to open it with a browser
	PrintURL bool `json:"print_url"`
	// ExpiresAt is the expiration date of the API key created during login
	ExpiresAt *time.Time `json:"expires_at"`
}

func loginCommand() *core.Command {
	return &core.Command{
		Groups: []string{"config"},
		Short:  `Login to scaleway`,
		Long: `Start an interactive connection to scaleway to initialize the active profile of the config
A webpage will open while the CLI will wait for a response.
Once you connected to Scaleway, the profile should be configured.
`,
		Namespace:            "login",
		AllowAnonymousClient: true,
		ArgsType:             reflect.TypeOf(loginArgs{}),
		ArgSpecs: core.ArgSpecs{
			{
				Name:  "port",
				Short: "The port number used to wait for browser's response",
			},
			{
				Name:  "expires-at",
				Short: "Expiration date of the API key (ISO 8601 or relative like +1y, +90d). Required when your organization enforces max-api-key-expiration-duration.",
			},
		},
		SeeAlsos: []*core.SeeAlso{
			{
				Short:   "Init profile manually",
				Command: "scw init",
			},
		},
		Run: func(ctx context.Context, argsI any) (any, error) {
			args := argsI.(*loginArgs)

			opts := []webcallback.Options(nil)
			if args.Port > 0 {
				opts = append(opts, webcallback.WithPort(args.Port))
			}

			wb := webcallback.New(opts...)
			err := wb.Start()
			if err != nil {
				return nil, err
			}

			callbackURL := fmt.Sprintf("http://localhost:%d/callback", wb.Port())

			params := url.Values{}
			params.Add("redirectToUrl", callbackURL)
			accountURL := "https://account.scaleway.com/authenticate?" + params.Encode()

			logger.Debugf("Web server started, waiting for callback on %s\n", callbackURL)

			if args.PrintURL {
				fmt.Println(accountURL)
			} else {
				err = open.Start(accountURL)
				if err != nil {
					logger.Warningf(
						"Failed to open web url, you may not have a default browser configured",
					)
					logger.Warningf("You can open it: " + accountURL)
				}
			}

			fmt.Println("waiting for callback from browser...")
			token, err := wb.Wait(ctx)
			if err != nil {
				return nil, err
			}

			rawToken, err := base64.StdEncoding.DecodeString(token)
			if err != nil {
				return nil, err
			}

			tt := Token{}

			err = json.Unmarshal(rawToken, &tt)
			if err != nil {
				return nil, err
			}

			buildInfo := core.ExtractBuildInfo(ctx)
			client, err := scw.NewClient(
				scw.WithJWT(tt.Token),
				scw.WithUserAgent(buildInfo.GetUserAgent()),
			)
			if err != nil {
				return nil, err
			}

			api := iam.NewAPI(client)
			req := &iam.CreateAPIKeyRequest{
				UserID:      &tt.Jwt.AudienceID,
				Description: "Generated by the Scaleway CLI",
			}
			if args.ExpiresAt != nil {
				req.ExpiresAt = args.ExpiresAt
			}
			apiKey, err := api.CreateAPIKey(req)
			if err != nil {
				return nil, err
			}

			resp, err := initCommand.Command().Run(ctx, &initCommand.Args{
				AccessKey:      apiKey.AccessKey,
				SecretKey:      *apiKey.SecretKey,
				ProjectID:      apiKey.DefaultProjectID,
				OrganizationID: apiKey.DefaultProjectID,
				Region:         scw.RegionFrPar,
				Zone:           scw.ZoneFrPar1,
			})
			if err != nil {
				// Cleanup API Key if init failed
				logger.Warningf("Init failed, cleaning API key.\n")
				cleanErr := api.DeleteAPIKey(&iam.DeleteAPIKeyRequest{
					AccessKey: apiKey.AccessKey,
				})
				if cleanErr != nil {
					logger.Warningf("Failed to clean API key: %s\n", err.Error())
				}

				return nil, err
			}

			return resp, nil
		},
	}
}

type Token struct {
	Jwt struct {
		AudienceID string    `json:"audienceId"`
		CreatedAt  time.Time `json:"createdAt"`
		ExpiresAt  time.Time `json:"expiresAt"`
		IP         string    `json:"ip"`
		IssuerID   string    `json:"issuerId"`
		Jti        string    `json:"jti"`
		UpdatedAt  time.Time `json:"updatedAt"`
		UserAgent  string    `json:"userAgent"`
	} `json:"jwt"`
	RenewToken string `json:"renewToken"`
	Token      string `json:"token"`
}
