package config

import (
	"errors"
	"fmt"
	sharedConfig "lunar/shared-model/config"
	"lunar/toolkit-core/urltree"

	"github.com/rs/zerolog/log"
)

type EndpointPolicy struct {
	URL       string
	Remedies  []sharedConfig.Remedy
	Diagnosis []sharedConfig.Diagnosis
}

type EndpointPolicyTree = urltree.EndpointTree[EndpointPolicy]

func BuildEndpointPolicyTree(
	endpoints []sharedConfig.EndpointConfig,
) (*EndpointPolicyTree, error) {
	endpointPolicyTree := newEndpointPolicyTree()
	for _, endpoint := range endpoints {
		err := checkForDuplicates(endpointPolicyTree, endpoint)
		if err != nil {
			return nil, err
		}
		var endpointPolicy *map[urltree.Method]EndpointPolicy
		existingEndpointPolicy := endpointPolicyTree.Lookup(endpoint.URL)
		if existingEndpointPolicy.Value != nil {
			existingPolicy := *existingEndpointPolicy.Value
			existingPolicy[urltree.Method(endpoint.Method)] = EndpointPolicy{
				URL:       endpoint.URL,
				Remedies:  endpoint.Remedies,
				Diagnosis: endpoint.Diagnosis,
			}
			endpointPolicy = &existingPolicy
		} else {
			endpointPolicy = &map[urltree.Method]EndpointPolicy{
				urltree.Method(endpoint.Method): {
					URL:       endpoint.URL,
					Remedies:  endpoint.Remedies,
					Diagnosis: endpoint.Diagnosis,
				},
			}
		}
		err = endpointPolicyTree.InsertDeclaredURL(endpoint.URL, endpointPolicy)
		if err != nil {
			joinedErr := errors.Join(fmt.Errorf(
				"failed to build endpoint policy tree. Error in endpoint %v %v",
				endpoint.Method,
				endpoint.URL,
			), err)
			return nil, joinedErr
		}
	}
	return endpointPolicyTree, nil
}

func newEndpointPolicyTree() *EndpointPolicyTree {
	return urltree.NewEndpointTree[EndpointPolicy]()
}

func checkForDuplicates(
	policyTree *urltree.EndpointTree[EndpointPolicy],
	endpoint sharedConfig.EndpointConfig,
) error {
	remedies, existingURL := existingRemedies(
		policyTree,
		endpoint.Method,
		endpoint.URL,
	)
	for _, remedy := range endpoint.Remedies {
		for _, existingRemedy := range remedies {
			if !existingRemedy.IsTypeUndefined() &&
				existingRemedy.Type() == remedy.Type() {
				return fmt.Errorf(
					"some URLs might match both \"%v\" and \"%v\" "+
						"This would cause a conflict between %v remedies \"%v\" and \"%v\"",
					endpoint.URL,
					existingURL,
					remedy.Type().String(),
					remedy.GetName(),
					existingRemedy.GetName(),
				)
			}
			log.Warn().Msgf("Some URLs might match both \"%v\" and \"%v\"."+
				"In that case remedies and diagnoses will run "+
				"in the order they are written",
				endpoint.URL,
				existingURL,
			)
		}
	}
	return nil
}

func existingRemedies(
	policyTree *urltree.EndpointTree[EndpointPolicy],
	methodArg string,
	url string,
) ([]sharedConfig.Remedy, string) {
	lookupResult := policyTree.Lookup(url)
	if lookupResult.Value == nil {
		return []sharedConfig.Remedy{}, ""
	}
	policy := *lookupResult.Value
	method := urltree.Method(methodArg)
	return policy[method].Remedies, policy[method].URL
}
