package apis

import (
	"log/slog"
	"net/http"
	"slices"

	"github.com/pocketbase/pocketbase/core"
	"github.com/pocketbase/pocketbase/tools/auth"
	"github.com/pocketbase/pocketbase/tools/security"
	"golang.org/x/oauth2"
)

type otpResponse struct {
	Enabled  bool  `json:"enabled"`
	Duration int64 `json:"duration"` // in seconds
}

type mfaResponse struct {
	Enabled  bool  `json:"enabled"`
	Duration int64 `json:"duration"` // in seconds
}

type passwordResponse struct {
	IdentityFields []string `json:"identityFields"`
	Enabled        bool     `json:"enabled"`
}

type oauth2Response struct {
	Providers []providerInfo `json:"providers"`
	Enabled   bool           `json:"enabled"`
}

type providerInfo struct {
	Name        string `json:"name"`
	DisplayName string `json:"displayName"`
	Logo        string `json:"logo"`
	State       string `json:"state"`
	AuthURL     string `json:"authURL"`

	// @todo
	// deprecated: use AuthURL instead
	// AuthUrl will be removed after dropping v0.22 support
	AuthUrl string `json:"authUrl"`

	// technically could be omitted if the provider doesn't support PKCE,
	// but to avoid breaking existing typed clients we'll return them as empty string
	CodeVerifier        string `json:"codeVerifier"`
	CodeChallenge       string `json:"codeChallenge"`
	CodeChallengeMethod string `json:"codeChallengeMethod"`
}

type authMethodsResponse struct {
	Password passwordResponse `json:"password"`
	OAuth2   oauth2Response   `json:"oauth2"`
	MFA      mfaResponse      `json:"mfa"`
	OTP      otpResponse      `json:"otp"`

	// legacy fields
	// @todo remove after dropping v0.22 support
	AuthProviders    []providerInfo `json:"authProviders"`
	UsernamePassword bool           `json:"usernamePassword"`
	EmailPassword    bool           `json:"emailPassword"`
}

func (amr *authMethodsResponse) fillLegacyFields() {
	amr.EmailPassword = amr.Password.Enabled && slices.Contains(amr.Password.IdentityFields, "email")

	amr.UsernamePassword = amr.Password.Enabled && slices.Contains(amr.Password.IdentityFields, "username")

	if amr.OAuth2.Enabled {
		// clone without the logo
		legacyProviders := make([]providerInfo, len(amr.OAuth2.Providers))
		for i, p := range amr.OAuth2.Providers {
			legacyProviders[i] = p
			legacyProviders[i].Logo = ""
		}

		amr.AuthProviders = legacyProviders
	}
}

func recordAuthMethods(e *core.RequestEvent) error {
	collection, err := findAuthCollection(e)
	if err != nil {
		return err
	}

	result := authMethodsResponse{
		Password: passwordResponse{
			IdentityFields: make([]string, 0, len(collection.PasswordAuth.IdentityFields)),
		},
		OAuth2: oauth2Response{
			Providers: make([]providerInfo, 0, len(collection.OAuth2.Providers)),
		},
		OTP: otpResponse{
			Enabled: collection.OTP.Enabled,
		},
		MFA: mfaResponse{
			Enabled: collection.MFA.Enabled,
		},
	}

	if collection.PasswordAuth.Enabled {
		result.Password.Enabled = true
		result.Password.IdentityFields = collection.PasswordAuth.IdentityFields
	}

	if collection.OTP.Enabled {
		result.OTP.Duration = collection.OTP.Duration
	}

	if collection.MFA.Enabled {
		result.MFA.Duration = collection.MFA.Duration
	}

	if !collection.OAuth2.Enabled {
		result.fillLegacyFields()

		return e.JSON(http.StatusOK, result)
	}

	result.OAuth2.Enabled = true

	for _, config := range collection.OAuth2.Providers {
		provider, err := config.InitProvider()
		if err != nil {
			e.App.Logger().Debug(
				"Failed to setup OAuth2 provider",
				slog.String("name", config.Name),
				slog.String("error", err.Error()),
			)
			continue // skip provider
		}

		info := providerInfo{
			Name:        config.Name,
			DisplayName: provider.DisplayName(),
			Logo:        provider.Logo(),
			State:       security.RandomString(30),
		}

		if info.DisplayName == "" {
			info.DisplayName = config.Name
		}

		urlOpts := []oauth2.AuthCodeOption{}

		// custom providers url options
		switch config.Name {
		case auth.NameApple:
			// see https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms#3332113
			urlOpts = append(urlOpts, oauth2.SetAuthURLParam("response_mode", "form_post"))
		}

		if provider.PKCE() {
			info.CodeVerifier = security.RandomString(43)
			info.CodeChallenge = security.S256Challenge(info.CodeVerifier)
			info.CodeChallengeMethod = "S256"
			urlOpts = append(urlOpts,
				oauth2.SetAuthURLParam("code_challenge", info.CodeChallenge),
				oauth2.SetAuthURLParam("code_challenge_method", info.CodeChallengeMethod),
			)
		}

		info.AuthURL = provider.BuildAuthURL(
			info.State,
			urlOpts...,
		) + "&redirect_uri=" // empty redirect_uri so that users can append their redirect url

		info.AuthUrl = info.AuthURL

		result.OAuth2.Providers = append(result.OAuth2.Providers, info)
	}

	result.fillLegacyFields()

	return e.JSON(http.StatusOK, result)
}
