package service

import (
	"sort"
	"strconv"
	"strings"
	"sync"

	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/buserr"
	"github.com/1Panel-dev/1Panel/agent/constant"
	"github.com/1Panel-dev/1Panel/agent/global"
	"github.com/1Panel-dev/1Panel/agent/utils/firewall"
	forwardClient "github.com/1Panel-dev/1Panel/agent/utils/firewall/forwarding"
)

type IForwardingService interface {
	LoadBaseInfo() (dto.FirewallBaseInfo, error)
	SearchWithPage(search dto.ForwardRuleSearch) (int64, interface{}, error)
	Operate(req dto.ForwardRuleOperate) error
	Enable() error
	Replay() error
}

type ForwardingService struct {
	adapterFactory func() (forwardClient.Adapter, error)
	filterFactory  func() (firewall.FilterClient, error)
}

func NewIForwardingService() IForwardingService {
	return &ForwardingService{
		adapterFactory: newForwardingAdapter,
		filterFactory:  firewall.NewFirewallClient,
	}
}

func newForwardingAdapter() (forwardClient.Adapter, error) {
	client, err := firewall.NewFirewallClient()
	if err != nil {
		return nil, err
	}
	return forwardClient.NewAdapter(client.Name())
}

func (s *ForwardingService) LoadBaseInfo() (dto.FirewallBaseInfo, error) {
	baseInfo := dto.FirewallBaseInfo{Version: "-", Name: "-"}
	adapter, err := s.adapterFactory()
	if err != nil {
		global.LOG.Errorf("load forwarding failed, err: %v", err)
		return baseInfo, nil
	}
	filter, err := s.filterFactory()
	if err != nil {
		global.LOG.Errorf("load firewall status failed, err: %v", err)
		return baseInfo, nil
	}
	baseInfo.IsExist = true
	baseInfo.Name = adapter.Name()

	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		defer wg.Done()
		baseInfo.PingStatus = firewall.LoadPingStatus()
		baseInfo.Version, _ = filter.Version()
	}()
	go func() {
		defer wg.Done()
		baseInfo.IsActive, _ = filter.Status()
		baseInfo.IsInit, baseInfo.IsBind = adapter.InitStatus()
	}()
	wg.Wait()
	return baseInfo, nil
}

func (s *ForwardingService) SearchWithPage(req dto.ForwardRuleSearch) (int64, interface{}, error) {
	adapter, err := s.adapterFactory()
	if err != nil {
		return 0, nil, err
	}
	rules, err := adapter.List()
	if err != nil {
		return 0, nil, err
	}
	if req.Strategy != "" {
		return 0, nil, nil
	}

	var filtered []forwardClient.Rule
	for _, rule := range rules {
		if req.Info != "" && !strings.Contains(rule.Port, req.Info) &&
			!strings.Contains(rule.TargetPort, req.Info) && !strings.Contains(rule.TargetIP, req.Info) {
			continue
		}
		filtered = append(filtered, rule)
	}
	total := len(filtered)
	start, end := (req.Page-1)*req.PageSize, req.Page*req.PageSize
	if start > total {
		return int64(total), make([]dto.ForwardRule, 0), nil
	}
	if end > total {
		end = total
	}
	pageRules := filtered[start:end]
	var items []dto.ForwardRule
	if pageRules != nil {
		items = make([]dto.ForwardRule, 0, len(pageRules))
	}
	for _, rule := range pageRules {
		items = append(items, dto.ForwardRule{
			Num:        rule.Num,
			Protocol:   rule.Protocol,
			Port:       rule.Port,
			TargetIP:   rule.TargetIP,
			TargetPort: rule.TargetPort,
			Interface:  rule.Interface,
		})
	}
	return int64(total), items, nil
}

func (s *ForwardingService) Operate(req dto.ForwardRuleOperate) error {
	adapter, err := s.adapterFactory()
	if err != nil {
		return err
	}

	rules, _ := adapter.List()
	kept := rules[:0]
	for _, rule := range rules {
		shouldKeep := true
		for i := range req.Rules {
			reqRule := &req.Rules[i]
			if reqRule.TargetIP == "" {
				reqRule.TargetIP = "127.0.0.1"
			}
			if reqRule.Operation == "remove" && requestMatchesForwardRule(*reqRule, rule) {
				shouldKeep = false
				break
			}
		}
		if shouldKeep {
			kept = append(kept, rule)
		}
	}

	for _, rule := range kept {
		for _, reqRule := range req.Rules {
			if reqRule.Operation != "remove" && requestMatchesForwardRule(reqRule, rule) {
				return buserr.New("ErrRecordExist")
			}
		}
	}

	sort.SliceStable(req.Rules, func(i, j int) bool {
		if req.Rules[i].Operation == "remove" && req.Rules[j].Operation != "remove" {
			return true
		}
		if req.Rules[i].Operation != "remove" && req.Rules[j].Operation == "remove" {
			return false
		}
		n1, _ := strconv.Atoi(req.Rules[i].Num)
		n2, _ := strconv.Atoi(req.Rules[j].Num)
		return n1 > n2
	})

	for _, rule := range req.Rules {
		for _, protocol := range strings.Split(rule.Protocol, "/") {
			targetIP := rule.TargetIP
			if targetIP == "" {
				targetIP = "127.0.0.1"
			}
			err := adapter.Operate(forwardClient.Rule{
				Num:        rule.Num,
				Protocol:   protocol,
				Port:       rule.Port,
				TargetIP:   targetIP,
				TargetPort: rule.TargetPort,
				Interface:  rule.Interface,
			}, rule.Operation)
			if err == nil {
				continue
			}
			if req.ForceDelete {
				global.LOG.Error(err)
				continue
			}
			return err
		}
	}
	return nil
}

func requestMatchesForwardRule(req dto.ForwardRuleOperation, rule forwardClient.Rule) bool {
	for _, protocol := range strings.Split(req.Protocol, "/") {
		if req.Port == rule.Port && req.TargetPort == rule.TargetPort && req.TargetIP == rule.TargetIP &&
			protocol == rule.Protocol && req.Interface == rule.Interface {
			return true
		}
	}
	return false
}

func (s *ForwardingService) Enable() error {
	adapter, err := s.adapterFactory()
	if err != nil {
		return err
	}
	if err := adapter.Enable(); err != nil {
		return err
	}
	if adapter.Name() != "firewalld" {
		_ = settingRepo.Update("IptablesForwardStatus", constant.StatusEnable)
	}
	return nil
}

func (s *ForwardingService) Replay() error {
	adapter, err := s.adapterFactory()
	if err != nil {
		return err
	}
	if err := adapter.Replay(); err != nil {
		return err
	}
	if adapter.Name() == "firewalld" {
		return nil
	}
	status, _ := settingRepo.GetValueByKey("IptablesForwardStatus")
	if status == constant.StatusEnable {
		return adapter.Enable()
	}
	return nil
}
