package service

import (
	"context"
	"fmt"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"net"
	"os"
	"os/exec"
	"strings"
	"sync"
)

type IpService interface {
	// AddIp 添加一个IP地址到网络接口并持久化。
	AddIp(ctx context.Context, ip string) error
	// DeleteIp 从网络接口删除一个IP地址并移除持久化配置。
	DeleteIp(ctx context.Context, ip string) error
}

func NewIpService(
	service *Service,
	viper *viper.Viper,
) IpService {
	return &ipService{
		Service:          service,
		networkInterface: viper.GetString("ip.network_interface"),
		ipLabel:          viper.GetString("ip.ip_label"),
		scriptPath:       viper.GetString("ip.script_path"),
	}
}

type ipService struct {
	*Service
	fileMux          sync.Mutex
	networkInterface string
	ipLabel          string
	scriptPath       string
}

// AddIp 负责添加一个IP地址到指定的网络接口，并将其持久化。
func (s *ipService) AddIp(ctx context.Context, ip string) error {
	if net.ParseIP(ip) == nil {
		return fmt.Errorf("无效的IP地址: %s", ip)
	}

	fullIp := ip + "/32"
	addCmdStr := fmt.Sprintf("sudo ip addr add %s dev %s label \"%s\"", fullIp, s.networkInterface, s.ipLabel)

	s.logger.Info("准备执行添加命令: %s\n", zap.String("addCmdStr", addCmdStr))

	// 执行实时添加IP的命令
	cmd := exec.CommandContext(ctx, "sudo", "ip", "addr", "add", fullIp, "dev", s.networkInterface, "label", s.ipLabel)
	output, err := cmd.CombinedOutput()
	if err != nil {
		s.logger.Error("执行添加命令失败: %s\n", zap.String("addCmdStr", addCmdStr), zap.Error(err))
		return fmt.Errorf("执行添加命令失败 '%s': %w. 输出: %s", addCmdStr, err, string(output))
	}

	// 持久化IP地址到脚本文件
	s.logger.Info("尝试将IP持久化到脚本: %s\n", zap.String("scriptPath", s.scriptPath))
	if err := s.makePersistent(addCmdStr); err != nil {
		return fmt.Errorf("持久化IP失败: %w", err)
	}

	return nil
}

// DeleteIp 负责从网络接口删除一个IP地址，并从持久化脚本中移除它。
func (s *ipService) DeleteIp(ctx context.Context, ip string) error {
	if net.ParseIP(ip) == nil {
		return fmt.Errorf("无效的IP地址: %s", ip)
	}

	fullIp := ip + "/32"
	// 这是要执行的删除命令
	delCmdStr := fmt.Sprintf("sudo ip addr del %s dev %s", fullIp, s.networkInterface)
	// 这是要在脚本文件中查找并删除的添加命令
	addCmdStrInScript := fmt.Sprintf("sudo ip addr add %s dev %s label \"%s\"", fullIp, s.networkInterface, s.ipLabel)

	s.logger.Info("准备执行删除命令: ", zap.String("delCmdStr", delCmdStr))

	// 执行实时删除IP的命令
	cmd := exec.CommandContext(ctx, "sudo", "ip", "addr", "del", fullIp, "dev", s.networkInterface)
	output, err := cmd.CombinedOutput()
	if err != nil {
		// 如果错误信息是 "Cannot assign requested address"，通常意味着IP已经不存在了。
		// 在这种情况下，我们不应该中止，而是应该继续清理脚本。
		// 对于其他错误，我们则返回。
		if !strings.Contains(string(output), "Cannot assign requested address") {
			return fmt.Errorf("执行删除命令失败 '%s': %w. 输出: %s", delCmdStr, err, string(output))
		}
		s.logger.Warn("警告: IP %s 已不在网络接口上，继续清理持久化脚本。\n", zap.String("ip", ip))
	} else {
		s.logger.Info("成功从网络接口删除IP地址。")
	}

	// 从持久化脚本中移除IP
	s.logger.Info("尝试从脚本 %s 中移除IP持久化记录\n", zap.String("scriptPath", s.scriptPath))
	if err := s.removeFromPersistent(addCmdStrInScript); err != nil {
		return fmt.Errorf("从持久化脚本中移除IP失败: %w", err)
	}

	s.logger.Info("成功从脚本中移除IP持久化记录。")

	return nil
}

// makePersistent 将IP添加命令写入到持久化脚本中。
func (s *ipService) makePersistent(cmdToAdd string) error {
	s.fileMux.Lock()
	defer s.fileMux.Unlock()

	content, err := os.ReadFile(s.scriptPath)
	if err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("持久化脚本不存在: %s", s.scriptPath)
		}
		return fmt.Errorf("读取持久化脚本失败 %s: %w", s.scriptPath, err)
	}

	if strings.Contains(string(content), cmdToAdd) {
		// 如果命令已经在脚本中，那么不需要再次添加。
		return nil
	}

	lines := strings.Split(string(content), "\n")
	var newLines []string
	inserted := false
	for _, line := range lines {
		if strings.TrimSpace(line) == "exit 0" && !inserted {
			newLines = append(newLines, cmdToAdd)
			inserted = true
		}
		newLines = append(newLines, line)
	}

	if !inserted {
		newLines = append(newLines, cmdToAdd)
	}

	newContent := strings.Join(newLines, "\n")
	return os.WriteFile(s.scriptPath, []byte(newContent), 0644)
}

// removeFromPersistent 从持久化脚本中删除指定的命令。
func (s *ipService) removeFromPersistent(cmdToRemove string) error {
	s.fileMux.Lock()
	defer s.fileMux.Unlock()

	content, err := os.ReadFile(s.scriptPath)
	if err != nil {
		if os.IsNotExist(err) {
			// 如果脚本文件不存在，那么命令肯定也不在里面，可以直接返回成功。
			return nil
		}
		return fmt.Errorf("读取持久化脚本失败 %s: %w", s.scriptPath, err)
	}

	lines := strings.Split(string(content), "\n")
	var newLines []string
	found := false
	for _, line := range lines {
		// 完全匹配要删除的行，然后跳过它
		if line == cmdToRemove {
			found = true
			continue
		}
		newLines = append(newLines, line)
	}

	if !found {
		// 如果未找到要删除的行，那么命令肯定也不在里面，可以直接返回成功。
		return nil
	}

	newContent := strings.Join(newLines, "\n")
	return os.WriteFile(s.scriptPath, []byte(newContent), 0644)
}
