package controller

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"strings"

	"github.com/1Panel-dev/1Panel/core/global"
	"github.com/1Panel-dev/1Panel/core/utils/controller/manager"
	"github.com/1Panel-dev/1Panel/core/utils/ssh"
)

type Controller interface {
	Name() string
	IsActive(serviceName string) (bool, error)
	IsEnable(serviceName string) (bool, error)
	IsExist(serviceName string) (bool, error)
	Status(serviceName string) (string, error)

	Operate(operate, serviceName string) error

	Reload() error
}

func New() (Controller, error) {
	managerOptions := []string{"systemctl", "rc-service", "service"}
	for _, item := range managerOptions {
		if _, err := exec.LookPath(item); err != nil {
			continue
		}
		switch item {
		case "systemctl":
			return manager.NewSystemd(), nil
		case "rc-service":
			return manager.NewOpenrc(), nil
		case "service":
			return manager.NewSysvinit(), nil
		}
	}
	return nil, errors.New("not support such manager initializatio")
}

func NewWithClient(client *ssh.SSHClient) (Controller, error) {
	managerOptions := []string{"systemctl", "rc-service", "service"}
	for _, item := range managerOptions {
		stdout, err := client.Runf("which %s", item)
		if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) {
			continue
		}
		switch item {
		case "systemctl":
			mgr := manager.NewSystemd()
			mgr.Client = client
			return mgr, nil
		case "rc-service":
			mgr := manager.NewOpenrc()
			mgr.Client = client
			return mgr, nil
		case "service":
			mgr := manager.NewSysvinit()
			mgr.Client = client
			return mgr, nil
		}
	}
	return nil, errors.New("not support such manager initializatio")
}

func Handle(operate, serviceName string) error {
	service, err := LoadServiceName(serviceName)
	if err != nil {
		return err
	}
	client, err := New()
	if err != nil {
		return err
	}
	return client.Operate(operate, service)
}
func HandleStart(serviceName string) error {
	service, err := LoadServiceName(serviceName)
	if err != nil {
		return err
	}
	return Handle("start", service)
}
func HandleStop(serviceName string) error {
	service, err := LoadServiceName(serviceName)
	if err != nil {
		return err
	}
	return Handle("stop", service)
}
func HandleRestart(serviceName string) error {
	service, err := LoadServiceName(serviceName)
	if err != nil {
		return err
	}
	return Handle("restart", service)
}

func CheckExist(serviceName string) (bool, error) {
	service, err := LoadServiceName(serviceName)
	if err != nil {
		return false, err
	}
	client, err := New()
	if err != nil {
		return false, err
	}
	b, er := client.IsExist(service)
	return b, er
}
func CheckActive(serviceName string) (bool, error) {
	service, err := LoadServiceName(serviceName)
	if err != nil {
		return false, err
	}
	client, err := New()
	if err != nil {
		return false, err
	}
	return client.IsActive(service)
}
func CheckEnable(serviceName string) (bool, error) {
	service, err := LoadServiceName(serviceName)
	if err != nil {
		return false, err
	}
	client, err := New()
	if err != nil {
		return false, err
	}
	return client.IsEnable(service)
}

func Reload() error {
	client, err := New()
	if err != nil {
		return err
	}
	return client.Reload()
}

func RestartPanel(core, agent, reload bool) {
	client, err := New()
	if err != nil {
		global.LOG.Errorf("load client for controller failed, err: %v", err)
		return
	}
	if reload {
		if err := client.Reload(); err != nil {
			global.LOG.Errorf("restart 1panel service failed, err: %v", err)
			return
		}
	}
	if agent {
		if err := client.Operate("restart", "1panel-agent"); err != nil {
			global.LOG.Errorf("restart 1panel agent service failed, err: %v", err)
			return
		}
	}
	if core {
		if err := client.Operate("restart", "1panel-core"); err != nil {
			global.LOG.Errorf("restart 1panel core service failed, err: %v", err)
			return
		}
	}
}

func LoadServiceName(keyword string) (string, error) {
	client, err := New()
	if err != nil {
		return "", err
	}

	processedName := loadProcessedName(client.Name(), keyword)
	exist, _ := client.IsExist(processedName)
	if exist {
		return processedName, nil
	}
	alistName := loadFromPredefined(client, keyword)
	if len(alistName) != 0 {
		return alistName, nil
	}
	return "", fmt.Errorf("find such service for %s failed", keyword)
}

func loadProcessedName(mgr, keyword string) string {
	keyword = strings.ToLower(keyword)
	if strings.HasSuffix(keyword, ".service.socket") {
		keyword = strings.TrimSuffix(keyword, ".service.socket") + ".socket"
	}
	if mgr != "systemd" {
		keyword = strings.TrimSuffix(keyword, ".service")
		return keyword
	}
	if !strings.HasSuffix(keyword, ".service") && !strings.HasSuffix(keyword, ".socket") {
		keyword += ".service"
	}
	return keyword
}

func loadFromPredefined(mgr Controller, keyword string) string {
	predefinedMap := map[string][]string{
		"clam":         {"clamav-daemon.service", "clamd@scan.service", "clamd"},
		"freshclam":    {"clamav-freshclam.service", "freshclam.service"},
		"fail2ban":     {"fail2ban.service", "fail2ban"},
		"supervisor":   {"supervisord.service", "supervisor.service", "supervisord", "supervisor"},
		"ssh":          {"sshd.service", "ssh.service", "sshd", "ssh"},
		"1panel-core":  {"1panel-core.service"},
		"1panel-agent": {"1panel-agent.service"},
		"docker":       {"docker.service", "dockerd"},
	}
	if val, ok := predefinedMap[keyword]; ok {
		for _, item := range val {
			if exist, _ := mgr.IsExist(item); exist {
				return item
			}
		}
	}
	return ""
}

// GetServicePath returns the configuration file path for the specified service.
// If serviceName is empty, it returns the default directory path based on the system's service manager.
// For non-empty serviceName, it retrieves the exact service file path.
// Parameters:
//   - serviceName: Name of the service. If empty, returns the default directory.
//
// Returns:
//   - string: The service configuration file path.
//   - error: Error if the service manager is unsupported or command execution fails.
func GetServicePath(serviceName string) (string, error) {
	if serviceName == "" {
		client, err := New()
		if err != nil {
			return "", err
		}
		switch client.Name() {
		case "systemd":
			return "/etc/systemd/system/", nil
		case "openrc", "sysvinit":
			return "/etc/init.d/", nil
		default:
			return "", fmt.Errorf("unsupported manager: %s", client.Name())
		}
	}
	service, err := LoadServiceName(serviceName)
	if err != nil {
		return "", err
	}
	client, err := New()
	if err != nil {
		return "", err
	}
	switch client.Name() {
	case "systemd":
		stdout, err := exec.Command("systemctl", "show", "-p", "FragmentPath", service).Output()
		if err != nil {
			return "", err
		}
		parts := strings.SplitN(string(stdout), "=", 2)
		if len(parts) != 2 {
			return "", fmt.Errorf("unexpected output: %s", string(stdout))
		}
		return strings.TrimSpace(parts[1]), nil
	case "openrc", "sysvinit":
		return fmt.Sprintf("/etc/init.d/%s", service), nil
	default:
		return "", fmt.Errorf("unsupported manager: %s", client.Name())
	}
}

func SelectInitScript(keyword string) (string, error) {
	client, err := New()
	if err != nil {
		return "", err
	}
	switch client.Name() {
	case "systemd":
		keyword = strings.TrimSuffix(keyword, ".service") + ".service"
	case "openrc":
		keyword = strings.TrimSuffix(keyword, ".service") + ".openrc"
	case "sysvinit":
		if _, err := os.Stat("/etc/rc.common"); err == nil {
			keyword = strings.TrimSuffix(keyword, ".service") + ".procd"
		} else {
			keyword = strings.TrimSuffix(keyword, ".service") + ".init"
		}
	default:
		return "", fmt.Errorf("unsupported manager: %s", client.Name())
	}
	return keyword, nil
}

func GetScriptName(keyword string) (string, error) {
	client, err := New()
	if err != nil {
		return "", err
	}
	switch client.Name() {
	case "systemd":
		keyword = strings.TrimSuffix(keyword, ".service") + ".service"
	case "openrc", "sysvinit":
		lastDotIdx := strings.LastIndex(keyword, ".")
		if lastDotIdx != -1 {
			keyword = keyword[:lastDotIdx]
		}
	default:
		return "", fmt.Errorf("unsupported manager: %s", client.Name())
	}
	return keyword, nil
}