// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package authn

import (
	"context"
	"encoding/base64"
	"errors"
	"strings"
)

type TokenType uint32

const (
	// AccessToken represents token generated by user.
	AccessToken TokenType = iota
	// PersonalAccessToken represents token generated by user for automation.
	PersonalAccessToken
)

const PatPrefix = "pat_"

func (t TokenType) String() string {
	switch t {
	case AccessToken:
		return "access token"
	case PersonalAccessToken:
		return "pat"
	default:
		return "unknown"
	}
}

type Role uint32

const (
	UserRole Role = iota + 1
	SuperAdminRole
)

type Session struct {
	Type         TokenType
	PatID        string
	UserID       string
	DomainID     string
	DomainUserID string
	SuperAdmin   bool
	Verified     bool
	Role         Role
}

// Authn is magistrala authentication library.
type Authentication interface {
	Authenticate(ctx context.Context, token string) (Session, error)
}

const authSep = ":"

type AuthPrefix int

const (
	Unknown AuthPrefix = iota
	BasicAuth
	DomainAuth
)

var authPrefixStrings = [3]string{
	"Unknown",
	"Basic",
	"Domain",
}

// String returns the string representation (e.g., "Basic") of the AuthPrefix.
func (a AuthPrefix) String() string {
	if int(a) < len(authPrefixStrings) {
		return authPrefixStrings[a]
	}
	return "Unknown"
}

// ErrNotEncoded acts similarly to EOF - it does indicate there is no suffix in
// the token, but that does not have to be treated as the error in some cases.
// If token is not base64-encoded, the token is returned as a key alongside with the error.
var ErrNotEncoded = errors.New("token is not encoded with suffix")

func AuthUnpack(token string) (AuthPrefix, string, string, error) {
	var auth AuthPrefix
	for i, pref := range authPrefixStrings {
		if strings.HasPrefix(token, pref) {
			token = token[len(pref):]
			auth = AuthPrefix(i)
			break
		}
	}
	payload, err := base64.StdEncoding.DecodeString(token)
	if err != nil {
		return Unknown, token, "", err
	}
	id, key, found := strings.Cut(string(payload), authSep)
	if !found {
		return auth, id, key, ErrNotEncoded
	}
	return auth, id, key, nil
}

func AuthPack(prefix AuthPrefix, id, key string) string {
	return prefix.String() + base64.StdEncoding.EncodeToString([]byte(id+":"+key))
}
