package iam

import (
	"fmt"

	"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
	"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
)

type S3Resolver interface {
	GetUserAddress(account, user string) (string, error)
}

func ConvertToS3Chain(p Policy, resolver S3Resolver) (*chain.Chain, error) {
	if err := p.Validate(ResourceBasedPolicyType); err != nil {
		return nil, err
	}

	var engineChain chain.Chain

	for _, statement := range p.Statement {
		status := formStatus(statement)

		actions, actionInverted := statement.action()
		if err := validateS3ActionNames(actions); err != nil {
			return nil, err
		}
		ruleAction := chain.Actions{Inverted: actionInverted, Names: actions}

		resources, resourceInverted := statement.resource()
		if err := validateS3ResourceNames(resources); err != nil {
			return nil, err
		}
		ruleResource := chain.Resources{Inverted: resourceInverted, Names: resources}

		groupedConditions, err := convertToS3ChainCondition(statement.Conditions, resolver)
		if err != nil {
			return nil, err
		}
		splitConditions := splitGroupedConditions(groupedConditions)

		principals, principalCondFn, err := getS3PrincipalsAndConditionFunc(statement, resolver)
		if err != nil {
			return nil, err
		}

		for _, principal := range principals {
			for _, conditions := range splitConditions {
				r := chain.Rule{
					Status:    status,
					Actions:   ruleAction,
					Resources: ruleResource,
					Condition: append([]chain.Condition{principalCondFn(principal)}, conditions...),
				}
				engineChain.Rules = append(engineChain.Rules, r)
			}
		}
	}

	return &engineChain, nil
}

func getS3PrincipalsAndConditionFunc(statement Statement, resolver S3Resolver) ([]string, formPrincipalConditionFunc, error) {
	var principals []string
	var op chain.ConditionType
	statementPrincipal, inverted := statement.principal()
	if _, ok := statementPrincipal[Wildcard]; ok { // this can be true only if 'inverted' false
		principals = []string{Wildcard}
		op = chain.CondStringLike
	} else {
		for principalType, principal := range statementPrincipal {
			if principalType != AWSPrincipalType {
				return nil, nil, fmt.Errorf("unsupported principal type '%s'", principalType)
			}
			parsedPrincipal, err := formS3Principal(principal, resolver)
			if err != nil {
				return nil, nil, fmt.Errorf("parse principal: %w", err)
			}
			principals = append(principals, parsedPrincipal...)
		}

		op = chain.CondStringEquals
		if inverted {
			op = chain.CondStringNotEquals
		}
	}

	return principals, func(principal string) chain.Condition {
		return chain.Condition{
			Op:     op,
			Object: chain.ObjectRequest,
			Key:    s3.PropertyKeyOwner,
			Value:  principal,
		}
	}, nil
}

func convertToS3ChainCondition(c Conditions, resolver S3Resolver) ([]GroupedConditions, error) {
	return convertToChainConditions(c, func(gr GroupedConditions) (GroupedConditions, error) {
		for i := range gr.Conditions {
			if gr.Conditions[i].Key == condKeyAWSPrincipalARN {
				gr.Conditions[i].Key = s3.PropertyKeyOwner
				val, err := formPrincipalOwner(gr.Conditions[i].Value, resolver)
				if err != nil {
					return GroupedConditions{}, err
				}
				gr.Conditions[i].Value = val
			}
		}

		return gr, nil
	})
}

func formS3Principal(principal []string, resolver S3Resolver) ([]string, error) {
	res := make([]string, len(principal))

	var err error
	for i := range principal {
		if res[i], err = formPrincipalOwner(principal[i], resolver); err != nil {
			return nil, err
		}
	}

	return res, nil
}

func formPrincipalOwner(principal string, resolver S3Resolver) (string, error) {
	account, user, err := parsePrincipalAsIAMUser(principal)
	if err != nil {
		return "", err
	}

	address, err := resolver.GetUserAddress(account, user)
	if err != nil {
		return "", fmt.Errorf("get user address: %w", err)
	}

	return address, nil
}

func validateS3ResourceNames(names []string) error {
	for i := range names {
		if err := validateResource(names[i]); err != nil {
			return err
		}
	}

	return nil
}

func validateS3ActionNames(names []string) error {
	for i := range names {
		if err := validateAction(names[i]); err != nil {
			return err
		}
	}

	return nil
}