package common

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/Dreamacro/clash/common/utils"
	C "github.com/Dreamacro/clash/constant"
)

type Port struct {
	*Base
	adapter  string
	port     string
	isSource bool
	portList []utils.Range[uint16]
}

func (p *Port) RuleType() C.RuleType {
	if p.isSource {
		return C.SrcPort
	}
	return C.DstPort
}

func (p *Port) Match(metadata *C.Metadata) bool {
	if p.isSource {
		return p.matchPortReal(metadata.SrcPort)
	}
	return p.matchPortReal(metadata.DstPort)
}

func (p *Port) Adapter() string {
	return p.adapter
}

func (p *Port) Payload() string {
	return p.port
}

func (p *Port) matchPortReal(portRef string) bool {
	port, _ := strconv.Atoi(portRef)

	for _, pr := range p.portList {
		if pr.Contains(uint16(port)) {
			return true
		}
	}

	return false
}

func NewPort(port string, adapter string, isSource bool) (*Port, error) {
	ports := strings.Split(port, "/")
	if len(ports) > 28 {
		return nil, fmt.Errorf("%s, too many ports to use, maximum support 28 ports", errPayload.Error())
	}

	var portRange []utils.Range[uint16]
	for _, p := range ports {
		if p == "" {
			continue
		}

		subPorts := strings.Split(p, "-")
		subPortsLen := len(subPorts)
		if subPortsLen > 2 {
			return nil, errPayload
		}

		portStart, err := strconv.ParseUint(strings.Trim(subPorts[0], "[ ]"), 10, 16)
		if err != nil {
			return nil, errPayload
		}

		switch subPortsLen {
		case 1:
			portRange = append(portRange, *utils.NewRange(uint16(portStart), uint16(portStart)))
		case 2:
			portEnd, err := strconv.ParseUint(strings.Trim(subPorts[1], "[ ]"), 10, 16)
			if err != nil {
				return nil, errPayload
			}

			portRange = append(portRange, *utils.NewRange(uint16(portStart), uint16(portEnd)))
		}
	}

	if len(portRange) == 0 {
		return nil, errPayload
	}

	return &Port{
		Base:     &Base{},
		adapter:  adapter,
		port:     port,
		isSource: isSource,
		portList: portRange,
	}, nil
}

var _ C.Rule = (*Port)(nil)
