package router

import (
	"errors"
	"fmt"

	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
	cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
	policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
)

func newTarget(ct ape.ChainTarget) (policyengine.Target, error) {
	var target policyengine.Target
	switch ct.TargetType {
	case ape.TargetTypeContainer:
		var cid cidSDK.ID
		err := cid.DecodeString(ct.Name)
		if err != nil {
			return target, fmt.Errorf("invalid cid format: %s", target.Name)
		}
		target.Type = policyengine.Container
	case ape.TargetTypeGroup:
		target.Type = policyengine.Group
	case ape.TargetTypeNamespace:
		target.Type = policyengine.Namespace
	case ape.TargetTypeUser:
		target.Type = policyengine.User
	default:
		return target, fmt.Errorf("unsupported target type: %v", ct.TargetType)
	}
	target.Name = ct.Name
	return target, nil
}

type morphReaderDecorator struct {
	policyengine.MorphRuleChainStorageReader

	bearerTokenTarget policyengine.Target

	bearerTokenChains []*chain.Chain
}

func newMorphReaderDecorator(r policyengine.MorphRuleChainStorageReader, override bearer.APEOverride) (*morphReaderDecorator, error) {
	if r == nil {
		return nil, errors.New("empty morph chain rule reader")
	}
	t, err := newTarget(override.Target)
	if err != nil {
		return nil, err
	}

	bearerTokenChains := make([]*chain.Chain, len(override.Chains))
	for i := range override.Chains {
		chain := new(chain.Chain)
		if err := chain.DecodeBytes(override.Chains[i].Raw); err != nil {
			return nil, fmt.Errorf("invalid ape chain: %w", err)
		}
		bearerTokenChains[i] = chain
	}

	return &morphReaderDecorator{
		MorphRuleChainStorageReader: r,
		bearerTokenTarget:           t,
		bearerTokenChains:           bearerTokenChains,
	}, nil
}

func (m *morphReaderDecorator) ListMorphRuleChains(name chain.Name, target policyengine.Target) ([]*chain.Chain, error) {
	if len(m.bearerTokenChains) > 0 && m.bearerTokenTarget.Type == target.Type {
		if m.bearerTokenTarget.Name != target.Name {
			return nil, fmt.Errorf("unexpected bearer token target: %s", m.bearerTokenTarget.Name)
		}
		return m.bearerTokenChains, nil
	}
	return m.MorphRuleChainStorageReader.ListMorphRuleChains(name, target)
}

// BearerChainFeedRouter creates a chain router emplacing bearer token rule chains.
// Bearer token chains override only container target chains within Policy contract. This means the order of checking
// is as follows:
//
//  1. Local overrides;
//  2. Policy contract chains for a namespace target (as namespace chains have higher priority);
//  3. Bearer token chains for a container target - if they're not defined, then it checks Policy contract chains;
//  4. Checks for the remaining targets.
func BearerChainFeedRouter(localOverrideStorage policyengine.LocalOverrideStorage, morphChainStorage policyengine.MorphRuleChainStorageReader, override bearer.APEOverride) (policyengine.ChainRouter, error) {
	mr, err := newMorphReaderDecorator(morphChainStorage, override)
	if err != nil {
		return nil, fmt.Errorf("create morph reader with bearer override error: %w", err)
	}
	return policyengine.NewDefaultChainRouterWithLocalOverrides(mr, localOverrideStorage), nil
}