/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package model

import (
	"strings"

	meshproto "github.com/apache/dubbo-admin/api/mesh/v1alpha1"
	"github.com/apache/dubbo-admin/pkg/common/constants"
	coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
)

type SearchConditionRuleReq struct {
	coremodel.PageReq
	Mesh     string `form:"mesh"`
	Keywords string `form:"keywords"`
}

func (s *SearchConditionRuleReq) PageRequest() coremodel.PageReq {
	return s.PageReq
}

func NewSearchConditionRuleReq() *SearchConditionRuleReq {
	return &SearchConditionRuleReq{
		PageReq: coremodel.PageReq{PageSize: 15},
	}
}

type ConditionRuleSearchResp struct {
	CreateTime string `json:"createTime"`
	Enabled    bool   `json:"enabled"`
	RuleName   string `json:"ruleName"`
	Scope      string `json:"scope"`
}

type ConditionRuleResp struct {
	Conditions    []string `json:"conditions"`
	ConfigVersion string   `json:"configVersion"`
	Enabled       bool     `json:"enabled"`
	Key           string   `json:"key"`
	Runtime       bool     `json:"runtime"`
	Scope         string   `json:"scope"`
}

type ServiceArgumentRoute struct {
	Routes []ServiceArgument `json:"routes"`
}

type ServiceArgument struct {
	Conditions   []RouteCondition `json:"conditions"`
	Destinations []Destination    `json:"destinations"`
	Method       string           `json:"method"`
}

// ToExpression Convert ServiceArgument to expression string
func (sa *ServiceArgument) ToExpression() string {
	expression := "method=" + sa.Method

	// Add route conditions
	for _, condition := range sa.Conditions {
		expression += " & " + condition.string()
	}

	// Add destinations if any
	if len(sa.Destinations) > 0 {
		expression += " => "

		// Process each destination
		for destIdx, destination := range sa.Destinations {
			for condIdx, condition := range destination.Conditions {
				if destIdx > 0 || condIdx > 0 {
					expression += " & "
				}
				expression += condition.string()
			}

			// If there are multiple destinations, separate them (using comma as separator)
			if destIdx < len(sa.Destinations)-1 {
				expression += ", "
			}
		}
	}

	return expression
}

func (sa *ServiceArgument) toFrom() *meshproto.ConditionRuleFrom {
	res := "method=" + sa.Method
	if len(sa.Conditions) != 0 {
		for i := 0; len(sa.Conditions) > i; i++ {
			res += " & " + sa.Conditions[i].string()
		}
	}
	return &meshproto.ConditionRuleFrom{
		Match: res,
	}
}

func (sa *ServiceArgument) toTo() []*meshproto.ConditionRuleTo {
	res := make([]*meshproto.ConditionRuleTo, 0, len(sa.Destinations))
	for _, destination := range sa.Destinations {
		match := ""
		for _, condition := range destination.Conditions {
			if match == "" {
				match += condition.string()
			} else {
				match += " & " + condition.string()
			}
		}
		res = append(res, &meshproto.ConditionRuleTo{
			Match:  match,
			Weight: destination.Weight,
		})
	}
	return res
}

type RouteCondition struct {
	Index    string `json:"index"`
	Relation string `json:"relation"`
	Value    string `json:"value"`
}

func (r *RouteCondition) string() string {
	if r.Relation == constants.Equal {
		return "arguments[" + r.Index + "]" + constants.Equal + r.Value
	} else {
		return "arguments[" + r.Index + "]" + constants.NotEqual + r.Value
	}
}

type Destination struct {
	Conditions []DestinationCondition `json:"conditions"`
	Weight     int32                  `json:"weight"`
}

type DestinationCondition struct {
	Relation string `json:"relation"`
	Tag      string `json:"tag"`
	Value    string `json:"value"`
}

func (d *DestinationCondition) string() string {
	if d.Relation == constants.Equal {
		return d.Tag + constants.Equal + d.Value
	} else {
		return d.Tag + constants.NotEqual + d.Value
	}
}

func (s *ServiceArgumentRoute) ToConditionV3x1Condition() []*meshproto.ConditionRule {
	res := make([]*meshproto.ConditionRule, 0, len(s.Routes))
	for _, route := range s.Routes {
		res = append(res, &meshproto.ConditionRule{
			From: route.toFrom(),
			To:   route.toTo(),
		})
	}
	return res
}

func ConditionV3x1ToServiceArgumentRoute(mesh []*meshproto.ConditionRule) *ServiceArgumentRoute {
	res := &ServiceArgumentRoute{
		Routes: make([]ServiceArgument, 0, len(mesh)),
	}
	for i := range mesh {
		method, _ := mesh[i].IsMatchMethod()
		cond := ServiceArgument{
			Conditions:   matchValueToRouteCondition(mesh[i].From.Match),
			Destinations: make([]Destination, 0, len(mesh[i].To)),
			Method:       method,
		}
		for _, to := range mesh[i].To {
			cond.Destinations = append(cond.Destinations, Destination{
				Conditions: matchValueToDestinationCondition(to.Match),
				Weight:     to.Weight,
			})
		}
		res.Routes = append(res.Routes, cond)
	}
	return res
}

func matchValueToRouteCondition(val string) []RouteCondition {
	subsets := strings.Split(val, "&")
	res := make([]RouteCondition, 0, len(subsets))
	for _, subset := range subsets {
		if index := strings.Index(subset, constants.NotEqual); index != -1 {
			res = append(res, RouteCondition{
				Index:    strings.Trim(subset[:index], " "),
				Relation: constants.NotEqual,
				Value:    strings.Trim(subset[index+len(constants.NotEqual):], " "),
			})
		} else if index := strings.Index(subset, constants.Equal); index != -1 {
			res = append(res, RouteCondition{
				Index:    strings.Trim(subset[:index], " "),
				Relation: constants.Equal,
				Value:    strings.Trim(subset[index+len(constants.Equal):], " "),
			})
		}
	}
	return res
}

func matchValueToDestinationCondition(val string) []DestinationCondition {
	subsets := strings.Split(val, "&")
	res := make([]DestinationCondition, 0, len(subsets))
	for _, subset := range subsets {
		if index := strings.Index(subset, constants.NotEqual); index != -1 {
			res = append(res, DestinationCondition{
				Tag:      strings.Trim(subset[:index], " "),
				Relation: constants.NotEqual,
				Value:    strings.Trim(subset[index+len(constants.NotEqual):], " "),
			})
		} else if index := strings.Index(subset, constants.Equal); index != -1 {
			res = append(res, DestinationCondition{
				Tag:      strings.Trim(subset[:index], " "),
				Relation: constants.Equal,
				Value:    strings.Trim(subset[index+len(constants.Equal):], " "),
			})
		}
	}
	return res
}

func GenConditionRuleToResp(data *meshproto.ConditionRoute) *CommonResp {
	if data == nil {
		return NewSuccessResp(nil)
	}
	return NewSuccessResp(ConditionRuleResp{
		Conditions:    data.Conditions,
		ConfigVersion: data.ConfigVersion,
		Enabled:       data.Enabled,
		Key:           data.Key,
		Runtime:       data.Runtime,
		Scope:         data.Scope,
	})
}

type ConditionRuleV3X1 struct {
	Conditions    []Condition `json:"conditions"`
	ConfigVersion string      `json:"configVersion"`
	Enabled       bool        `json:"enabled"`
	Force         bool        `json:"force"`
	Key           string      `json:"key"`
	Runtime       bool        `json:"runtime"`
	Scope         string      `json:"scope"`
}

type AffinityAware struct {
	Enabled bool   `json:"enabled"`
	Key     string `json:"key"`
}

type Condition struct {
	From ConditionFrom `json:"from"`
	To   []ConditionTo `json:"to"`
}

type ConditionFrom struct {
	Match string `json:"match"`
}

type ConditionTo struct {
	Match  string `json:"match"`
	Weight int32  `json:"weight"`
}

// ParseConditionExpression converts an expression string to a ServiceArgument struct
// Expression format: "method=methodName & condition1 => destination1"
func ParseConditionExpression(expression string) ServiceArgument {
	// Split into from and to parts (separated by "=>")
	parts := strings.Split(expression, "=>")
	fromPart := strings.TrimSpace(parts[0])
	toPart := ""
	if len(parts) > 1 {
		toPart = strings.TrimSpace(parts[1])
	}

	// Parse from part to get method and conditions
	method, conditions := parseFromPart(fromPart)

	// Parse to part to get destinations
	destinations := parseToPart(toPart)

	return ServiceArgument{
		Method:       method,
		Conditions:   conditions,
		Destinations: destinations,
	}
}

// parseFromPart parses the from part containing method and arguments conditions
func parseFromPart(fromPart string) (string, []RouteCondition) {
	conditions := []RouteCondition{}
	method := ""

	// Split conditions by "&"
	subConditions := strings.Split(fromPart, "&")

	for _, condition := range subConditions {
		condition = strings.TrimSpace(condition)

		// Check if it's a method condition
		if strings.HasPrefix(condition, "method=") {
			method = strings.TrimPrefix(condition, "method=")
			method = strings.TrimSpace(method)
		} else {
			// Parse arguments condition
			routeCond := parseRouteCondition(condition)
			if routeCond.Index != "" {
				conditions = append(conditions, routeCond)
			}
		}
	}

	return method, conditions
}

// parseRouteCondition parses a single route condition
func parseRouteCondition(condition string) RouteCondition {
	var relation string
	var relationOp string

	// Check if it's "=" or "!="
	if index := strings.Index(condition, constants.NotEqual); index != -1 {
		relation = constants.NotEqual
		relationOp = constants.NotEqual
	} else if index := strings.Index(condition, constants.Equal); index != -1 {
		relation = constants.Equal
		relationOp = constants.Equal
	} else {
		return RouteCondition{} // Invalid condition
	}

	// Find the position of the relation
	index := strings.Index(condition, relationOp)
	leftPart := strings.TrimSpace(condition[:index])
	rightPart := strings.TrimSpace(condition[index+len(relationOp):])

	// Parse arguments[index] format
	if strings.HasPrefix(leftPart, "arguments[") && strings.HasSuffix(leftPart, "]") {
		argIndex := leftPart[10 : len(leftPart)-1] // Extract index within brackets
		return RouteCondition{
			Index:    argIndex,
			Relation: relation,
			Value:    rightPart,
		}
	}

	// If not arguments format, return generic condition
	return RouteCondition{
		Index:    leftPart,
		Relation: relation,
		Value:    rightPart,
	}
}

// parseToPart parses the to part (destination conditions)
func parseToPart(toPart string) []Destination {
	if toPart == "" {
		return []Destination{}
	}

	// Split multiple conditions by "&"
	conditions := strings.Split(toPart, "&")
	destinationConditions := []DestinationCondition{}

	for _, condition := range conditions {
		condition = strings.TrimSpace(condition)
		destCond := parseDestinationCondition(condition)
		if destCond.Tag != "" {
			destinationConditions = append(destinationConditions, destCond)
		}
	}

	return []Destination{
		{
			Conditions: destinationConditions,
			Weight:     0, // Default weight is 0, can be adjusted as needed
		},
	}
}

// parseDestinationCondition parses destination condition
func parseDestinationCondition(condition string) DestinationCondition {
	var relation string
	var relationOp string

	// Check if it's "=" or "!="
	if index := strings.Index(condition, constants.NotEqual); index != -1 {
		relation = constants.NotEqual
		relationOp = constants.NotEqual
	} else if index := strings.Index(condition, constants.Equal); index != -1 {
		relation = constants.Equal
		relationOp = constants.Equal
	} else {
		return DestinationCondition{} // Invalid condition
	}

	// Find the position of the relation
	index := strings.Index(condition, relationOp)
	leftPart := strings.TrimSpace(condition[:index])
	rightPart := strings.TrimSpace(condition[index+len(relationOp):])

	return DestinationCondition{
		Tag:      leftPart,
		Relation: relation,
		Value:    rightPart,
	}
}
