package bitwarden

import (
	"strconv"
	"strings"
	"time"

	"github.com/cozy/cozy-stack/model/bitwarden/settings"
	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/oauth"
	"github.com/cozy/cozy-stack/model/permission"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/crypto"
	"github.com/golang-jwt/jwt/v5"
)

// BitwardenScope was the OAuth scope, hard-coded with the doctypes needed by
// the Bitwarden apps. The new scope is dynamic, taken from the cozy-pass web
// manifest.
var BitwardenScope = strings.Join([]string{
	consts.BitwardenProfiles,
	consts.BitwardenCiphers,
	consts.BitwardenFolders,
	consts.BitwardenOrganizations,
	consts.BitwardenContacts,
	consts.Konnectors,
	consts.AppsSuggestion,
	consts.Support,
}, " ")

// IsBitwardenClient returns true if the client can use the bitwarden refresh
// endpoint.
func IsBitwardenClient(client *oauth.Client, scope string) bool {
	// Help the transition from hard-coded scope
	if scope == BitwardenScope {
		return true
	}

	return oauth.GetLinkedAppSlug(client.SoftwareID) == consts.PassSlug
}

// ParseBitwardenDeviceType takes a deviceType (Bitwarden) and transforms it
// into a client_kind (Cozy).
// See https://github.com/bitwarden/server/blob/f37f33512046707eef69a2cb3944338de819439d/src/Core/Enums/DeviceType.cs
func ParseBitwardenDeviceType(deviceType string) string {
	device, err := strconv.Atoi(deviceType)
	if err == nil {
		switch device {
		case 0, 1, 15, 16:
			// 0 = Android
			// 1 = iOS
			// 15 = Android (amazon variant)
			// 16 = UWP
			return "mobile"
		case 6, 7, 8:
			// 6 = Windows
			// 7 = macOS
			// 8 = Linux
			return "desktop"
		case 2, 3, 4, 5, 19, 20:
			// 2 = Chrome extension
			// 3 = Firefox extension
			// 4 = Opera extension
			// 5 = Edge extension
			// 19 = Vivaldi extension
			// 20 = Safari extension
			return "browser"
		case 9, 10, 11, 12, 13, 14, 17, 18:
			// 9 = Chrome
			// 10 = Firefox
			// 11 = Opera
			// 12 = Edge
			// 13 = Internet Explorer
			// 14 = Unknown browser
			// 17 = Safari
			// 18 = Vivaldi
			return "web"
		}
	}
	return "unknown"
}

// CreateAccessJWT returns a new JSON Web Token that can be used with Bitwarden
// apps. It is an access token, with some additional custom fields.
// See https://github.com/bitwarden/jslib/blob/master/common/src/services/token.service.ts
func CreateAccessJWT(i *instance.Instance, c *oauth.Client) (string, error) {
	now := time.Now()
	name, err := i.SettingsPublicName()
	if err != nil || name == "" {
		name = "Anonymous"
	}
	var stamp string
	if settings, err := settings.Get(i); err == nil {
		stamp = settings.SecurityStamp
	}
	scope := BitwardenScope
	if slug := oauth.GetLinkedAppSlug(c.SoftwareID); slug != "" {
		scope = oauth.BuildLinkedAppScope(slug)
	}
	token, err := crypto.NewJWT(i.OAuthSecret, permission.BitwardenClaims{
		Claims: permission.Claims{
			RegisteredClaims: jwt.RegisteredClaims{
				Audience:  jwt.ClaimStrings{consts.AccessTokenAudience},
				Issuer:    i.Domain,
				NotBefore: jwt.NewNumericDate(now.Add(-60 * time.Second)),
				IssuedAt:  jwt.NewNumericDate(now),
				ExpiresAt: jwt.NewNumericDate(now.Add(consts.AccessTokenValidityDuration)),
				Subject:   i.ID(),
			},
			SStamp: stamp,
			Scope:  scope,
		},
		ClientID: c.CouchID,
		Name:     name,
		Email:    string(i.PassphraseSalt()),
		Verified: false,
		Premium:  false,
	})
	if err != nil {
		i.Logger().WithNamespace("oauth").
			Errorf("Failed to create the bitwarden access token: %s", err)
	}
	return token, err
}

// CreateRefreshJWT returns a new JSON Web Token that can be used with
// Bitwarden apps. It is a refresh token, with an additional security stamp.
func CreateRefreshJWT(i *instance.Instance, c *oauth.Client) (string, error) {
	var stamp string
	if settings, err := settings.Get(i); err == nil {
		stamp = settings.SecurityStamp
	}
	scope := BitwardenScope
	if slug := oauth.GetLinkedAppSlug(c.SoftwareID); slug != "" {
		scope = oauth.BuildLinkedAppScope(slug)
	}
	token, err := crypto.NewJWT(i.OAuthSecret, permission.Claims{
		RegisteredClaims: jwt.RegisteredClaims{
			Audience: jwt.ClaimStrings{consts.RefreshTokenAudience},
			Issuer:   i.Domain,
			IssuedAt: jwt.NewNumericDate(time.Now()),
			Subject:  c.CouchID,
		},
		SStamp: stamp,
		Scope:  scope,
	})
	if err != nil {
		i.Logger().WithNamespace("oauth").
			Errorf("Failed to create the bitwarden refresh token: %s", err)
	}
	return token, err
}
