package services

import (
	"context"
	"encoding/json"
	"fmt"
	"lan-manager/server/db"
	"lan-manager/server/utils"
	"os/exec"
	"runtime"
	"strings"
	"time"

	"net"

	"golang.org/x/net/icmp"
	"golang.org/x/net/ipv4"
)

func PingHost(ip string) bool {
	// Try system ping first (no special privileges needed on Linux)
	if ok := pingWithSystem(ip); ok {
		return true
	}
	// Fallback to raw ICMP
	return pingWithICMP(ip)
}

func pingWithSystem(ip string) bool {
	var cmd *exec.Cmd
	if runtime.GOOS == "windows" {
		cmd = exec.Command("ping", "-n", "1", "-w", "3000", ip)
	} else if runtime.GOOS == "darwin" {
		// macOS ping -W is in milliseconds
		cmd = exec.Command("ping", "-c", "1", "-W", "3000", ip)
	} else {
		// Linux ping -W is in seconds
		cmd = exec.Command("ping", "-c", "1", "-W", "3", ip)
	}
	err := cmd.Run()
	return err == nil
}

func pingWithICMP(ip string) bool {
	c, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
	if err != nil {
		return false
	}
	defer c.Close()

	m := &icmp.Message{
		Type: ipv4.ICMPTypeEcho,
		Code: 0,
		Body: &icmp.Echo{ID: 1, Seq: 1, Data: []byte("lan-manager")},
	}
	wb, err := m.Marshal(nil)
	if err != nil {
		return false
	}
	if _, err := c.WriteTo(wb, &net.IPAddr{IP: parseIP(ip)}); err != nil {
		return false
	}

	rb := make([]byte, 1500)
	_ = c.SetReadDeadline(time.Now().Add(3 * time.Second))
	n, _, err := c.ReadFrom(rb)
	if err != nil {
		return false
	}
	rm, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), rb[:n])
	if err != nil {
		return false
	}
	return rm.Type == ipv4.ICMPTypeEchoReply
}

func parseIP(ip string) []byte {
	parts := strings.Split(ip, ".")
	if len(parts) != 4 {
		return nil
	}
	res := make([]byte, 4)
	for i, p := range parts {
		var v byte
		fmt.Sscanf(p, "%d", &v)
		res[i] = v
	}
	return res
}

func StartPingService(interval int) {
	ticker := time.NewTicker(time.Duration(interval) * time.Second)
	go func() {
		for range ticker.C {
			rows, err := db.DB.Query(`SELECT id, ip, ssh_port, ssh_username, ssh_password FROM machines`)
			if err != nil {
				fmt.Printf("[Ping] query error: %v\n", err)
				continue
			}
			type machinePing struct {
				id            int64
				ip            string
				sshPort       int
				sshUsername   string
				sshPassword   string
			}
			list := []machinePing{}
			for rows.Next() {
				var m machinePing
				if err := rows.Scan(&m.id, &m.ip, &m.sshPort, &m.sshUsername, &m.sshPassword); err == nil {
					list = append(list, m)
				} else {
					fmt.Printf("[Ping] scan error: %v\n", err)
				}
			}
			rows.Close()
			fmt.Printf("[Ping] tick processed %d machines\n", len(list))

			for _, m := range list {
				online := PingHost(m.ip)
				var onlineInt int
				if online {
					onlineInt = 1
				}
				_, dbErr := db.DB.Exec(`UPDATE machines SET is_online = ?, last_ping_at = CURRENT_TIMESTAMP WHERE id = ?`, onlineInt, m.id)
				if dbErr != nil {
					fmt.Printf("[Ping] update status error for %s: %v\n", m.ip, dbErr)
				} else {
					fmt.Printf("[Ping] %s -> online=%v\n", m.ip, online)
				}

				// Auto fetch SSH info if credentials are saved
				if m.sshUsername != "" && m.sshPassword != "" {
					go func(mid int64, mip string, mport int, muser, mpass string) {
						plainPass, decryptErr := utils.Decrypt(mpass)
						if decryptErr != nil {
							fmt.Printf("[SSH] decrypt failed for %s:%d: %v\n", mip, mport, decryptErr)
							return
						}
						result, err := GetSSHInfo(mip, mport, muser, plainPass)
						if err != nil {
							fmt.Printf("[SSH] auto-fetch failed for %s:%d: %v\n", mip, mport, err)
							return
						}
						portsStr := ""
						if len(result.ListenPorts) > 0 {
							b, _ := json.Marshal(result.ListenPorts)
							portsStr = string(b)
							if len(portsStr) > 2 {
								portsStr = portsStr[1 : len(portsStr)-1]
							}
						}
						_, dbErr := db.DB.Exec(`UPDATE machines SET cpu_info=?, memory_info=?, disk_info=?, uptime=?, listen_ports=?, ssh_synced_at=CURRENT_TIMESTAMP, updated_at=CURRENT_TIMESTAMP WHERE id=?`,
							result.RawCPUInfo, result.RawMemoryInfo, result.RawDiskInfo, result.Uptime, portsStr, mid)
						if dbErr != nil {
							fmt.Printf("[SSH] auto-fetch db error for %s:%d: %v\n", mip, mport, dbErr)
						} else {
							fmt.Printf("[SSH] auto-fetch success for %s:%d\n", mip, mport)
						}
					}(m.id, m.ip, m.sshPort, m.sshUsername, m.sshPassword)
				}
			}
		}
	}()
}

func CleanupLogs(ctx context.Context, retentionDays int) {
	if retentionDays <= 0 {
		return
	}
	ticker := time.NewTicker(24 * time.Hour)
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
				_, _ = db.DB.Exec(`DELETE FROM operation_logs WHERE created_at < datetime('now', '-`+fmt.Sprintf("%d", retentionDays)+` days')`)
			}
		}
	}()
}
