package connector

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"os/user"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/beclab/Olares/cli/pkg/core/common"
	"github.com/beclab/Olares/cli/pkg/core/util"

	"github.com/shirou/gopsutil/v4/cpu"
	"github.com/shirou/gopsutil/v4/disk"
	"github.com/shirou/gopsutil/v4/host"
	"github.com/shirou/gopsutil/v4/mem"
)

type UbuntuVersion string
type DebianVersion string

func (u UbuntuVersion) String() string {
	switch u {
	case Ubuntu20:
		return "20."
	case Ubuntu22:
		return "22."
	case Ubuntu24:
		return "24."
	}
	return ""
}

func (d DebianVersion) String() string {
	switch d {
	case Debian11:
		return "11"
	case Debian12:
		return "12"
	case Debian13:
		return "13"
	}
	return ""
}

const (
	Ubuntu20 UbuntuVersion = "20."
	Ubuntu22 UbuntuVersion = "22."
	Ubuntu24 UbuntuVersion = "24."
	Ubuntu25 UbuntuVersion = "25."

	Debian9  DebianVersion = "9"
	Debian10 DebianVersion = "10"
	Debian11 DebianVersion = "11"
	Debian12 DebianVersion = "12"
	Debian13 DebianVersion = "13"
)

type Systems interface {
	IsSupport() error

	IsWindows() bool
	IsDarwin() bool
	IsWsl() bool
	IsPve() bool
	IsPveLxc() bool
	IsPveOrPveLxc() bool
	IsRaspbian() bool
	IsLinux() bool

	IsUbuntu() bool
	IsDebian() bool
	GetDebianVersionCode() string

	IsUbuntuVersionEqual(ver UbuntuVersion) bool
	IsDebianVersionEqual(ver DebianVersion) bool
	IsOsArchInvalid() bool

	SetHostname(v string)
	GetHostname() string
	GetOsType() string
	GetOsArch() string
	GetUsername() string
	GetHomeDir() string
	GetOsVersion() string
	GetPkgManager() string
	SetNATGateway(ip string)
	GetNATGateway() string

	GetOsPlatformFamily() string

	GetLocalIp() string

	CgroupCpuEnabled() bool
	CgroupMemoryEnabled() bool
	GetFsType() string
	GetDefaultZfsPrefixName() string
	GetTotalMemory() uint64

	Print()
	String() string
}

type SystemInfo struct {
	HostInfo   *HostInfo       `json:"host"`
	CpuInfo    *CpuInfo        `json:"cpu"`
	DiskInfo   *DiskInfo       `json:"disk"`
	MemoryInfo *MemoryInfo     `json:"memory"`
	FsInfo     *FileSystemInfo `json:"filesystem"`
	CgroupInfo *CgroupInfo     `json:"cgroup,omitempty"`
	LocalIp    string          `json:"local_ip"`
	NatGateway string          `json:"nat_gateway"`
	PkgManager string          `json:"pkg_manager"`
	IsOIC      bool            `json:"is_oic,omitempty"`
}

func (s *SystemInfo) IsSupport() error {
	if !s.IsLinux() && !s.IsDarwin() && !s.IsWindows() {
		return fmt.Errorf("unsupported os type '%s'", s.GetOsType())
	}

	if s.IsOsArchInvalid() {
		return fmt.Errorf("unsupported arch '%s'", s.GetOsArch())
	}

	//if !s.IsUbuntu() && !s.IsDebian() {
	//	return fmt.Errorf("unsupported os type '%s', exit ...", s.GetOsPlatformFamily())
	//}

	if s.IsUbuntu() {
		if !s.IsUbuntuVersionEqual(Ubuntu22) && !s.IsUbuntuVersionEqual(Ubuntu24) && !s.IsUbuntuVersionEqual(Ubuntu25) {
			return fmt.Errorf("unsupported ubuntu os version '%s'", s.GetOsVersion())
		}
	}

	if s.IsDebian() {
		if !s.IsDebianVersionEqual(Debian12) && !s.IsDebianVersionEqual(Debian13) {
			return fmt.Errorf("unsupported debian os version '%s'", s.GetOsVersion())
		}
	}

	return nil
}

func (s *SystemInfo) GetLocalIp() string {
	return s.LocalIp
}

func (s *SystemInfo) SetNATGateway(ip string) {
	s.NatGateway = ip
}
func (s *SystemInfo) GetNATGateway() string {
	return s.NatGateway
}

func (s *SystemInfo) GetHostname() string {
	return s.HostInfo.HostName
}

func (s *SystemInfo) SetHostname(v string) {
	s.HostInfo.HostName = v
}

func (s *SystemInfo) GetOsType() string {
	return s.HostInfo.OsType
}

func (s *SystemInfo) GetOsArch() string {
	return s.HostInfo.OsArch
}

func (s *SystemInfo) GetUsername() string {
	return s.HostInfo.CurrentUser
}

func (s *SystemInfo) GetHomeDir() string {
	return s.HostInfo.HomeDir
}

func (s *SystemInfo) IsOsArchInvalid() bool {
	return strings.EqualFold(s.HostInfo.OsArch, "")
}

func (s *SystemInfo) GetOsVersion() string {
	return s.HostInfo.OsVersion
}

func (s *SystemInfo) GetOsPlatformFamily() string {
	return s.HostInfo.OsPlatformFamily
}

func (s *SystemInfo) String() string {
	str, _ := json.Marshal(s)
	return string(str)
}

func (s *SystemInfo) IsWindows() bool {
	return s.HostInfo.OsType == common.Windows
}

func (s *SystemInfo) IsDarwin() bool {
	return s.HostInfo.OsType == common.Darwin
}

func (s *SystemInfo) IsPve() bool {
	return s.HostInfo.OsPlatform == common.PVE
}

func (s *SystemInfo) IsPveLxc() bool {
	return s.HostInfo.OsPlatform == common.PVE_LXC
}

func (s *SystemInfo) IsPveOrPveLxc() bool {
	return s.IsPve() || s.IsPveLxc()
}

func (s *SystemInfo) IsWsl() bool {
	return s.HostInfo.OsPlatform == common.WSL && !s.IsOIC
}

func (s *SystemInfo) IsRaspbian() bool {
	return s.HostInfo.OsPlatform == common.Raspbian
}

func (s *SystemInfo) IsLinux() bool {
	return s.HostInfo.OsType == common.Linux
}

func (s *SystemInfo) IsUbuntu() bool {
	return s.HostInfo.OsPlatformFamily == common.Ubuntu
}

func (s *SystemInfo) IsDebian() bool {
	return s.HostInfo.OsPlatformFamily == common.Debian
}

func (s *SystemInfo) IsUbuntuVersionEqual(ver UbuntuVersion) bool {
	return strings.Contains(s.HostInfo.OsVersion, ver.String())
}

func (s *SystemInfo) IsDebianVersionEqual(ver DebianVersion) bool {
	return strings.Contains(s.HostInfo.OsVersion, ver.String())
}

func (s *SystemInfo) GetDebianVersionCode() string {
	if !s.IsDebian() {
		return ""
	}

	if strings.Contains(s.HostInfo.OsVersion, string(Debian13)) {
		return "trixie"
	} else if strings.Contains(s.HostInfo.OsVersion, string(Debian12)) {
		return "bookworm"
	} else if strings.Contains(s.HostInfo.OsVersion, string(Debian11)) {
		return "bullseye"
	} else if strings.Contains(s.HostInfo.OsVersion, string(Debian10)) {
		return "buster"
	} else if strings.Contains(s.HostInfo.OsVersion, string(Debian9)) {
		return "stretch"
	} else {
		return "jessie"
	}
}

func (s *SystemInfo) CgroupCpuEnabled() bool {
	return s.CgroupInfo.CpuEnabled >= 1
}

func (s *SystemInfo) CgroupMemoryEnabled() bool {
	return s.CgroupInfo.MemoryEnabled >= 1
}

func (s *SystemInfo) GetFsType() string {
	return s.FsInfo.Type
}

func (s *SystemInfo) GetDefaultZfsPrefixName() string {
	return s.FsInfo.DefaultZfsPrefixName
}

func (s *SystemInfo) GetTotalMemory() uint64 {
	return s.MemoryInfo.Total
}

func (s *SystemInfo) GetPkgManager() string {
	return s.PkgManager
}

func (s *SystemInfo) Print() {
	fmt.Printf("os info, all: %s\n", s.HostInfo.OsInfo)
	fmt.Printf("host info, user: %s, hostname: %s, hostid: %s, os: %s, platform: %s, platformfamily: %s, version: %s, arch: %s, localip: %s\n",
		s.HostInfo.CurrentUser, s.HostInfo.HostName, s.HostInfo.HostId,
		s.HostInfo.OsType, s.HostInfo.OsPlatform, s.HostInfo.OsPlatformFamily, s.HostInfo.OsVersion, s.HostInfo.OsArch, s.LocalIp)
	fmt.Printf("kernel info, version: %s\n", s.HostInfo.OsKernel)
	fmt.Printf("virtual info, role: %s, system: %s\n", s.HostInfo.VirtualizationRole, s.HostInfo.VirtualizationSystem)

	fmt.Printf("cpu info, model: %s, logical count: %d, physical count: %d\n",
		s.CpuInfo.CpuModel, s.CpuInfo.CpuLogicalCount, s.CpuInfo.CpuPhysicalCount)

	fmt.Printf("disk info, total: %s, free: %s\n", util.FormatBytes(int64(s.DiskInfo.Total)), util.FormatBytes(int64(s.DiskInfo.Free)))
	fmt.Printf("fs info, fs: %s, zfsmount: %s\n", s.FsInfo.Type, s.FsInfo.DefaultZfsPrefixName)
	fmt.Printf("mem info, total: %s, free: %s\n", util.FormatBytes(int64(s.MemoryInfo.Total)), util.FormatBytes(int64(s.MemoryInfo.Free)))
	fmt.Printf("cgroup info, cpu: %d, mem: %d\n", s.CgroupInfo.CpuEnabled, s.CgroupInfo.MemoryEnabled)
	fmt.Printf("oic: %t\n", s.IsOIC)
	fmt.Printf("in wsl: %t\n", s.IsWsl())
}

func GetSystemInfo() *SystemInfo {
	var si = new(SystemInfo)
	si.HostInfo = getHost()
	si.CpuInfo = getCpu()
	si.DiskInfo = getDisk()
	si.MemoryInfo = getMem()
	si.FsInfo = getFs()

	localIP, err := util.GetLocalIP()
	if err != nil {
		panic(errors.Wrap(err, "failed to get local ip"))
	}
	si.LocalIp = localIP.String()

	if si.IsLinux() {
		si.CgroupInfo = getCGroups()
	}

	switch si.GetOsPlatformFamily() {
	case common.Ubuntu, common.Debian:
		si.PkgManager = "apt-get"
	case common.Fedora:
		si.PkgManager = "dnf"
	case common.CentOs, common.RHEL:
		si.PkgManager = "yum"
	default:
		si.PkgManager = "apt-get"
	}

	return si
}

type HostInfo struct {
	HostName             string `json:"hostname"`
	HostId               string `json:"hostid"`
	OsType               string `json:"os_type"`
	OsPlatform           string `json:"os_platform"`
	OsPlatformFamily     string `json:"os_platform_family"`
	OsVersion            string `json:"os_version"`
	OsArch               string `json:"os_arch"`
	VirtualizationRole   string `json:"virtualization_role"`
	VirtualizationSystem string `json:"virtualization_system"`
	OsKernel             string `json:"os_kernel"`
	OsInfo               string `json:"os_info"`
	CurrentUser          string `json:"current_user"`
	HomeDir              string `json:"home_dir"`
}

func getHost() *HostInfo {
	hostInfo, err := host.Info()
	if err != nil {
		panic(err)
	}

	cmd := exec.Command("sh", "-c", "echo $(uname -a) |tr -d '\\n'")
	output, _ := cmd.Output()
	// if err != nil {
	// 	panic(err)
	// }

	var _osType = hostInfo.OS
	var _osPlatform = hostInfo.Platform
	var _osPlatformFamily = hostInfo.PlatformFamily
	var _osKernel = hostInfo.KernelVersion

	u, _ := user.Current()

	return &HostInfo{
		HostName:             hostInfo.Hostname,
		HostId:               hostInfo.HostID,
		OsType:               _osType,                                           // darwin linux windows
		OsPlatform:           formatOsPlatform(_osType, _osPlatform, _osKernel), // darwin linux wsl raspbian pve
		OsPlatformFamily:     formatOsPlatformFamily(_osPlatform, _osPlatformFamily),
		OsVersion:            hostInfo.PlatformVersion,
		OsArch:               ArchAlias(hostInfo.KernelArch),
		VirtualizationRole:   hostInfo.VirtualizationRole,
		VirtualizationSystem: hostInfo.VirtualizationSystem,
		OsKernel:             hostInfo.KernelVersion,
		OsInfo:               string(output),
		CurrentUser:          u.Username,
		HomeDir:              u.HomeDir,
	}
}

func formatOsPlatform(osType, osPlatform, osKernel string) string {
	if osType == common.Darwin {
		return common.Darwin
	}

	if osPlatform == common.Raspbian {
		return common.Raspbian
	}

	if strings.Contains(osKernel, "pve") {
		if lxc := os.Getenv("container"); lxc == "lxc" {
			return common.PVE_LXC
		}
		return common.PVE
	}

	if strings.Contains(osKernel, "-WSL") {
		return common.WSL
	}

	return common.Linux
}

func formatOsPlatformFamily(osPlatform, osPlatformFamily string) string {
	if osPlatform == common.Darwin {
		return common.Darwin
	}

	if osPlatform == common.Raspbian {
		return osPlatformFamily
	}

	return osPlatform
}

type CpuInfo struct {
	CpuModel         string `json:"cpu_model"`
	CpuLogicalCount  int    `json:"cpu_logical_count"`
	CpuPhysicalCount int    `json:"cpu_physical_count"`
}

func getCpu() *CpuInfo {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	cpuInfo, _ := cpu.InfoWithContext(ctx)
	cpuLogicalCount, _ := cpu.CountsWithContext(ctx, true)
	cpuPhysicalCount, _ := cpu.CountsWithContext(ctx, false)

	var cpuModel = ""
	if cpuInfo != nil && len(cpuInfo) > 0 {
		cpuModel = cpuInfo[0].ModelName
	}

	return &CpuInfo{
		CpuModel:         cpuModel,
		CpuLogicalCount:  cpuLogicalCount,
		CpuPhysicalCount: cpuPhysicalCount,
	}
}

type DiskInfo struct {
	Total uint64 `json:"total"`
	Free  uint64 `json:"free"`
}

func getDisk() *DiskInfo {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	usageInfo, _ := disk.UsageWithContext(ctx, "/")

	var total uint64
	var free uint64
	if usageInfo != nil {
		total = usageInfo.Total
		free = usageInfo.Free
	}

	return &DiskInfo{
		Total: total,
		Free:  free,
	}
}

type MemoryInfo struct {
	Total uint64 `json:"total"`
	Free  uint64 `json:"free"`
}

func getMem() *MemoryInfo {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	memInfo, _ := mem.VirtualMemoryWithContext(ctx)

	var total uint64
	var free uint64
	if memInfo != nil {
		total = memInfo.Total
		free = memInfo.Free
	}

	return &MemoryInfo{
		Total: total,
		Free:  free,
	}
}

type FileSystemInfo struct {
	Type                 string `json:"type"`
	DefaultZfsPrefixName string `json:"default_zfs_prefix_name"`
}

func getFs() *FileSystemInfo {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	var fsType = "overlayfs"
	var zfsPrefixName = ""

	ps, _ := disk.PartitionsWithContext(ctx, true)
	if ps != nil && len(ps) > 0 {
		for _, p := range ps {
			if p.Mountpoint == "/var/lib" && p.Fstype == "zfs" {
				fsType = "zfs"
				zfsPrefixName = p.Device
				break
			}
		}
	}

	return &FileSystemInfo{
		Type:                 fsType,
		DefaultZfsPrefixName: zfsPrefixName,
	}
}

type CgroupInfo struct {
	CpuEnabled    int `json:"cpu_enabled"`
	MemoryEnabled int `json:"memory_enabled"`
}

func getCGroups() *CgroupInfo {

	file, err := os.Open("/proc/cgroups")
	if err != nil {
		return nil
	}
	defer file.Close()

	var cpuEnabled int64
	var memoryEnabled int64

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "#") {
			continue
		}
		fields := strings.Fields(line)
		if len(fields) < 4 {
			continue
		}
		switch fields[0] {
		case "cpu":
			cpuEnabled, _ = strconv.ParseInt(fields[3], 10, 64)
		case "memory":
			memoryEnabled, _ = strconv.ParseInt(fields[3], 10, 64)
		default:
			continue
		}
	}

	if err := scanner.Err(); err != nil {
		panic(err)
	}

	return &CgroupInfo{
		CpuEnabled:    int(cpuEnabled),
		MemoryEnabled: int(memoryEnabled),
	}
}

func ArchAlias(arch string) string {
	switch arch {
	case "aarch64", "armv7l", "arm64", "arm":
		return "arm64"
	case "x86_64", "amd64":
		fallthrough
	case "ppc64le":
		fallthrough
	case "s390x":
		return "amd64"
	default:
		return ""
	}
}
