package auth

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
)

type metadataStoreIdentityPlugin struct {
	MetadataIdentityTyeKey string
	MetadataIdentityValKey string
}

func NewMetadataStoreIdentityPlugin(tyeKey, valKey string) (IdentityPlugin, error) {
	if tyeKey == "" || valKey == "" {
		return nil, errors.New("identity_type key or identity_value key is empty")
	}
	return &metadataStoreIdentityPlugin{
		MetadataIdentityTyeKey: tyeKey,
		MetadataIdentityValKey: valKey,
	}, nil
}

func (d *metadataStoreIdentityPlugin) GetIdentity(_ context.Context, param *AccessParam) (*IdentityRet, error) {
	tyeI, ok := param.Metadata[d.MetadataIdentityTyeKey]
	if !ok {
		return nil, errors.New("identity_type not found in metadata")
	}
	tye, ok := tyeI.(AccessIdentityType)
	if !ok {
		return nil, errors.New("identity_type in metadata is not AccessIdentityType")
	}
	if !isAccessIdentityTyeValid(tye) {
		return nil, errors.New(fmt.Sprintf("identity_type %s invalid", tye))
	}

	valI, ok := param.Metadata[d.MetadataIdentityValKey]
	if !ok {
		return nil, errors.New("identity_value not found in metadata")
	}
	val, ok := valI.(string)
	if !ok {
		return nil, errors.New("identity_value in metadata is not string")
	}

	ret := IdentityRet{
		IdentityType:  tye,
		IdentityValue: val,
	}
	return &ret, nil
}

func isAccessIdentityTyeValid(tye AccessIdentityType) bool {
	if tye == AccessIdentityTypeUserID {
		return true
	}
	if tye == AccessIdentityTypeApiKeyAK {
		return true
	}
	if tye == AccessIdentityTypeAccessToken {
		return true
	}
	return false
}

type contextStoreIdentityPlugin struct {
	CtxIdentityKey interface{}
}

func NewContextStoreIdentityPlugin(ctxIdentityKey interface{}) (IdentityPlugin, error) {
	if ctxIdentityKey == nil {
		return nil, errors.New("ctxIdentityKey is nil")
	}
	return &contextStoreIdentityPlugin{
		CtxIdentityKey: ctxIdentityKey,
	}, nil
}

func NewContextStoreGetIdentityFunc(ctxDataKey interface{}) (GetIdentityFunc, error) {
	helper, err := NewContextStoreIdentityPlugin(ctxDataKey)
	if err != nil {
		return nil, err
	}
	return func(ctx context.Context, param *AccessParam) (*IdentityRet, error) {
		return helper.GetIdentity(ctx, param)
	}, nil
}

func (d *contextStoreIdentityPlugin) GetIdentity(ctx context.Context, _ *AccessParam) (*IdentityRet, error) {
	identI := ctx.Value(d.CtxIdentityKey)
	if identI == nil {
		return nil, errors.New("not found identity info in context")
	}
	marshal, err := json.Marshal(identI)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("marshal identity info in context err: %s", err.Error()))
	}
	var info ContextIdentityInfo
	err = json.Unmarshal(marshal, &info)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("unmarshal identity info in context to ContextIdentityInfo err: %s", err.Error()))
	}

	if !isAccessIdentityTyeValid(info.IdentityTye) {
		return nil, errors.New(fmt.Sprintf("identity_type %s invalid", info.IdentityTye))
	}

	ret := IdentityRet{
		IdentityType:  info.IdentityTye,
		IdentityValue: info.IdentityVal,
	}
	return &ret, nil
}

type ContextIdentityInfo struct {
	IdentityTye AccessIdentityType `json:"identity_type"`
	IdentityVal string             `json:"identity_value"`
}
