package handlers

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"lan-manager/server/db"
	"lan-manager/server/middleware"
	"lan-manager/server/models"
	"lan-manager/server/services"
	"lan-manager/server/utils"
)

type MachineHandler struct{}

func NewMachineHandler() *MachineHandler {
	return &MachineHandler{}
}

// sanitizeMachine removes sensitive fields for guest users
func sanitizeMachine(m *models.Machine) {
	m.IP = ""
	m.MAC = models.NullString{}
	m.Notes = models.NullString{}
	m.SSHUsername = models.NullString{}
	m.SSHPassword = models.NullString{}
	m.ListenPorts = models.NullString{}
	m.CreatedAt = time.Time{}
	m.UpdatedAt = time.Time{}
}

func (h *MachineHandler) List(c *gin.Context) {
	isAdmin := middleware.IsAdmin(c)
	osFilter := c.Query("os_type")
	search := c.Query("search")

	query := `SELECT id, hostname, ip, mac, os_type, os_version, notes, ssh_port, ssh_username, ssh_password, is_online, last_ping_at, cpu_info, memory_info, disk_info, uptime, listen_ports, ssh_synced_at, offline_count, total_offline_seconds, last_offline_at, last_offline_reason, created_at, updated_at FROM machines WHERE 1=1`
	args := []interface{}{}
	if osFilter != "" {
		query += ` AND os_type = ?`
		args = append(args, osFilter)
	}
	if search != "" {
		if isAdmin {
			query += ` AND (hostname LIKE ? OR ip LIKE ?)`
			args = append(args, "%"+search+"%", "%"+search+"%")
		} else {
			query += ` AND hostname LIKE ?`
			args = append(args, "%"+search+"%")
		}
	}
	query += ` ORDER BY created_at DESC`

	rows, err := db.DB.Query(query, args...)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	machines := []models.Machine{}
	for rows.Next() {
		var m models.Machine
		var lp, ss *time.Time
		var lo *time.Time
		err := rows.Scan(&m.ID, &m.Hostname, &m.IP, &m.MAC, &m.OsType, &m.OsVersion, &m.Notes, &m.SSHPort, &m.SSHUsername, &m.SSHPassword, &m.IsOnline, &lp, &m.CPUInfo, &m.MemoryInfo, &m.DiskInfo, &m.Uptime, &m.ListenPorts, &ss, &m.OfflineCount, &m.TotalOfflineSeconds, &lo, &m.LastOfflineReason, &m.CreatedAt, &m.UpdatedAt)
		if err != nil {
			continue
		}
		m.LastPingAt = lp
		m.SSHSyncedAt = ss
		m.LastOfflineAt = lo
		m.SSHPassword = models.NullString{} // never return password
		if !isAdmin {
			sanitizeMachine(&m)
		}
		machines = append(machines, m)
	}
	rows.Close()

	// Fetch service counts
	if len(machines) > 0 {
		scRows, err := db.DB.Query(`SELECT machine_id, COUNT(*) FROM services GROUP BY machine_id`)
		if err == nil {
			defer scRows.Close()
			for scRows.Next() {
				var mid int64
				var cnt int
				if err := scRows.Scan(&mid, &cnt); err == nil {
					for i := range machines {
						if machines[i].ID == mid {
							machines[i].ServiceCount = cnt
							break
						}
					}
				}
			}
		}
	}

	c.JSON(http.StatusOK, machines)
}

func (h *MachineHandler) Get(c *gin.Context) {
	isAdmin := middleware.IsAdmin(c)
	id, err := strconv.ParseInt(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}

	var m models.Machine
	var lp, ss *time.Time
	var lo *time.Time
	err = db.DB.QueryRow(`SELECT id, hostname, ip, mac, os_type, os_version, notes, ssh_port, ssh_username, ssh_password, is_online, last_ping_at, cpu_info, memory_info, disk_info, uptime, listen_ports, ssh_synced_at, offline_count, total_offline_seconds, last_offline_at, last_offline_reason, created_at, updated_at FROM machines WHERE id = ?`, id).
		Scan(&m.ID, &m.Hostname, &m.IP, &m.MAC, &m.OsType, &m.OsVersion, &m.Notes, &m.SSHPort, &m.SSHUsername, &m.SSHPassword, &m.IsOnline, &lp, &m.CPUInfo, &m.MemoryInfo, &m.DiskInfo, &m.Uptime, &m.ListenPorts, &ss, &m.OfflineCount, &m.TotalOfflineSeconds, &lo, &m.LastOfflineReason, &m.CreatedAt, &m.UpdatedAt)
	m.LastOfflineAt = lo
	if err == sql.ErrNoRows {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	m.LastPingAt = lp
	m.SSHSyncedAt = ss

	services := []models.Service{}
	if rows, err := db.DB.Query(`SELECT id, machine_id, name, port, protocol, notes FROM services WHERE machine_id = ?`, id); err == nil {
		defer rows.Close()
		for rows.Next() {
			var s models.Service
			if err := rows.Scan(&s.ID, &s.MachineID, &s.Name, &s.Port, &s.Protocol, &s.Notes); err == nil {
				if !isAdmin {
					s.Notes = ""
				}
				services = append(services, s)
			}
		}
	}

	relationships := []models.Relationship{}
	if isAdmin {
		if rows, err := db.DB.Query(`SELECT id, source_machine_id, target_machine_id, relation_type, source_port, target_port, notes FROM relationships WHERE source_machine_id = ? OR target_machine_id = ?`, id, id); err == nil {
			defer rows.Close()
			for rows.Next() {
				var r models.Relationship
				if err := rows.Scan(&r.ID, &r.SourceMachineID, &r.TargetMachineID, &r.RelationType, &r.SourcePort, &r.TargetPort, &r.Notes); err == nil {
					relationships = append(relationships, r)
				}
			}
		}
	}

	m.SSHPassword = models.NullString{} // never return password
	if !isAdmin {
		sanitizeMachine(&m)
	}

	c.JSON(http.StatusOK, gin.H{
		"machine":       m,
		"services":      services,
		"relationships": relationships,
	})
}

func (h *MachineHandler) OfflineLogs(c *gin.Context) {
	id, err := strconv.ParseInt(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	rows, err := db.DB.Query(`SELECT id, machine_id, reason, started_at, ended_at, duration_seconds FROM offline_logs WHERE machine_id = ? ORDER BY started_at DESC LIMIT 50`, id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()
	logs := []models.OfflineLog{}
	for rows.Next() {
		var l models.OfflineLog
		var endedAt *time.Time
		var dur *int
		if err := rows.Scan(&l.ID, &l.MachineID, &l.Reason, &l.StartedAt, &endedAt, &dur); err == nil {
			l.EndedAt = endedAt
			l.DurationSeconds = dur
			logs = append(logs, l)
		}
	}
	c.JSON(http.StatusOK, logs)
}

func (h *MachineHandler) Create(c *gin.Context) {
	var m models.Machine
	if err := c.ShouldBindJSON(&m); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	encPass, _ := utils.Encrypt(m.SSHPassword.String)
	passField := models.NullString{}
	passField.String = encPass
	passField.Valid = encPass != ""
	res, err := db.DB.Exec(`INSERT INTO machines (hostname, ip, mac, os_type, os_version, notes, ssh_port, ssh_username, ssh_password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		m.Hostname, m.IP, m.MAC, m.OsType, m.OsVersion, m.Notes, m.SSHPort, m.SSHUsername, passField)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	id, _ := res.LastInsertId()
	m.ID = id
	m.CreatedAt = time.Now()
	m.UpdatedAt = time.Now()

	middleware.LogOperation("create", "machine", &m.ID, m.Hostname, "", middleware.ToJSON(m), c.ClientIP(), middleware.CurrentUser(c))
	c.JSON(http.StatusCreated, m)
}

func (h *MachineHandler) Update(c *gin.Context) {
	id, err := strconv.ParseInt(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	var m models.Machine
	if err := c.ShouldBindJSON(&m); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var old models.Machine
	var oldPass string
	var lp, ss *time.Time
	_ = db.DB.QueryRow(`SELECT id, hostname, ip, mac, os_type, os_version, notes, ssh_port, ssh_username, ssh_password, is_online, last_ping_at, cpu_info, memory_info, disk_info, uptime, listen_ports, ssh_synced_at, created_at, updated_at FROM machines WHERE id = ?`, id).
		Scan(&old.ID, &old.Hostname, &old.IP, &old.MAC, &old.OsType, &old.OsVersion, &old.Notes, &old.SSHPort, &old.SSHUsername, &oldPass, &old.IsOnline, &lp, &old.CPUInfo, &old.MemoryInfo, &old.DiskInfo, &old.Uptime, &old.ListenPorts, &ss, &old.CreatedAt, &old.UpdatedAt)
	old.LastPingAt = lp
	old.SSHSyncedAt = ss

	// Keep old password if new one is empty
	passToSave := oldPass
	if m.SSHPassword.String != "" {
		encPass, err := utils.Encrypt(m.SSHPassword.String)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "encrypt failed"})
			return
		}
		passToSave = encPass
	}

	_, err = db.DB.Exec(`UPDATE machines SET hostname=?, ip=?, mac=?, os_type=?, os_version=?, notes=?, ssh_port=?, ssh_username=?, ssh_password=?, updated_at=CURRENT_TIMESTAMP WHERE id=?`,
		m.Hostname, m.IP, m.MAC, m.OsType, m.OsVersion, m.Notes, m.SSHPort, m.SSHUsername, passToSave, id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	m.ID = id
	old.SSHPassword = models.NullString{}
	m.SSHPassword = models.NullString{}
	middleware.LogOperation("update", "machine", &m.ID, m.Hostname, middleware.ToJSON(old), middleware.ToJSON(m), c.ClientIP(), middleware.CurrentUser(c))
	c.JSON(http.StatusOK, m)
}

func (h *MachineHandler) Delete(c *gin.Context) {
	id, err := strconv.ParseInt(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	var name string
	_ = db.DB.QueryRow(`SELECT hostname FROM machines WHERE id = ?`, id).Scan(&name)
	_, err = db.DB.Exec(`DELETE FROM machines WHERE id = ?`, id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	middleware.LogOperation("delete", "machine", &id, name, "", "", c.ClientIP(), middleware.CurrentUser(c))
	c.JSON(http.StatusOK, gin.H{"message": "deleted"})
}

func (h *MachineHandler) SSHInfo(c *gin.Context) {
	id, err := strconv.ParseInt(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	var req models.SSHInfoRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	var ip string
	var sshPort int
	var savedUser, savedPass string
	if err := db.DB.QueryRow(`SELECT ip, ssh_port, ssh_username, ssh_password FROM machines WHERE id = ?`, id).Scan(&ip, &sshPort, &savedUser, &savedPass); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "machine not found"})
		return
	}

	user := req.Username
	pass := req.Password

	// 如果前端没传密码，尝试用数据库保存的凭据
	if pass == "" {
		if savedUser == "" || savedPass == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "未保存 SSH 凭据，请手动输入"})
			return
		}
		plainPass, decErr := utils.Decrypt(savedPass)
		if decErr != nil || plainPass == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "未保存 SSH 凭据，请手动输入"})
			return
		}
		user = savedUser
		pass = plainPass
	}

	result, err := services.GetSSHInfo(ip, sshPort, user, pass)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Save credentials to machine record for auto-sync (only when user explicitly provided password)
	if req.Password != "" {
		encPass, _ := utils.Encrypt(req.Password)
		_, _ = db.DB.Exec(`UPDATE machines SET ssh_username=?, ssh_password=? WHERE id=?`, req.Username, encPass, id)
	}

	c.JSON(http.StatusOK, result)
}

func (h *MachineHandler) SyncSSH(c *gin.Context) {
	id, err := strconv.ParseInt(c.Param("id"), 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	var req models.SSHInfoResult
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	portsStr := ""
	if len(req.ListenPorts) > 0 {
		b, _ := json.Marshal(req.ListenPorts)
		portsStr = string(b)
		// remove brackets
		if len(portsStr) > 2 {
			portsStr = portsStr[1 : len(portsStr)-1]
		}
	}
	_, err = 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=?`,
		req.RawCPUInfo, req.RawMemoryInfo, req.RawDiskInfo, req.Uptime, portsStr, id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	middleware.LogOperation("update", "machine", &id, "sync-ssh", "", "", c.ClientIP(), middleware.CurrentUser(c))
	c.JSON(http.StatusOK, gin.H{"message": "synced"})
}
