package controllers

import (
	"encoding/json"
	"nodemaster/models"

	beego "github.com/beego/beego/v2/server/web"
)

// NodeController manages the current host node.
type NodeController struct {
	beego.Controller
}

// @Title GetNode
// @Description get current node info including runtime config_file path
// @Success 200 {object} models.NodeInfo
// @router / [get]
func (c *NodeController) Get() {
	n := models.GetLocalNode()
	c.Data["json"] = map[string]interface{}{
		"hostname":            n.Hostname,
		"description":         n.Description,
		"update_history":      n.UpdateHistory,
		"backup":              n.Backup,
		"config_file":         models.ConfigPath(),
		"restic_password_set": n.ResticPassword != "",
	}
	c.ServeJSON()
}

// @Title UpdateNode
// @Description update current node hostname, description, or restic_password (the repository password used to encrypt/decrypt all restic backup targets on this node)
// @Param body body models.NodeInfo true "fields to update"
// @Success 200 {string} update success
// @Failure 400 invalid body
// @router / [put]
func (c *NodeController) Put() {
	var n models.NodeInfo
	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &n); err != nil {
		c.Ctx.Output.SetStatus(400)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	if err := models.UpdateLocalNode(n); err != nil {
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Data["json"] = map[string]string{"message": "update success"}
	c.ServeJSON()
}

// @Title GetUpdates
// @Description list update history
// @Success 200 {object} []models.UpdateRecord
// @router /updates [get]
func (c *NodeController) GetUpdates() {
	c.Data["json"] = models.GetUpdateHistory()
	c.ServeJSON()
}

// @Title GetUpdate
// @Description get a single update record
// @Param id path string true "update record id"
// @Success 200 {object} models.UpdateRecord
// @Failure 404 not found
// @router /updates/:id [get]
func (c *NodeController) GetUpdate() {
	id := c.Ctx.Input.Param(":id")
	r, err := models.GetUpdateRecord(id)
	if err != nil {
		c.Ctx.Output.SetStatus(404)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Data["json"] = r
	c.ServeJSON()
}

// @Title AddUpdate
// @Description record a new update entry
// @Param body body models.UpdateRecord true "update record"
// @Success 201 {object} models.UpdateRecord
// @Failure 400 invalid body
// @router /updates [post]
func (c *NodeController) AddUpdate() {
	var r models.UpdateRecord
	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &r); err != nil {
		c.Ctx.Output.SetStatus(400)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	record, err := models.AddUpdateRecord(r)
	if err != nil {
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Ctx.Output.SetStatus(201)
	c.Data["json"] = record
	c.ServeJSON()
}

// @Title DeleteUpdate
// @Description delete an update record
// @Param id path string true "update record id"
// @Success 200 {string} delete success
// @Failure 404 not found
// @router /updates/:id [delete]
func (c *NodeController) DeleteUpdate() {
	id := c.Ctx.Input.Param(":id")
	if err := models.DeleteUpdateRecord(id); err != nil {
		c.Ctx.Output.SetStatus(404)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Data["json"] = map[string]string{"message": "delete success"}
	c.ServeJSON()
}

// @Title GetBackup
// @Description get node backup configuration
// @Success 200 {object} models.BackupConfig
// @router /backup [get]
func (c *NodeController) GetBackup() {
	c.Data["json"] = models.GetNodeBackupConfig()
	c.ServeJSON()
}

// @Title UpdateBackup
// @Description update node backup configuration
// @Param body body models.BackupConfig true "backup config"
// @Success 200 {string} update success
// @Failure 400 invalid body
// @router /backup [put]
func (c *NodeController) PutBackup() {
	var bc models.BackupConfig
	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &bc); err != nil {
		c.Ctx.Output.SetStatus(400)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	if err := models.UpdateNodeBackupConfig(bc); err != nil {
		c.Ctx.Output.SetStatus(500)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Data["json"] = map[string]string{"message": "update success"}
	c.ServeJSON()
}

// @Title Scan
// @Description scan compose folders and index discovered services; accepts optional {"folder":"/path"} body
// @Param body body object false "optional folder override"
// @Success 200 {object} models.ScanResult
// @router /scan [post]
func (c *NodeController) Scan() {
	var req struct {
		Folder string `json:"folder"`
	}
	_ = json.Unmarshal(c.Ctx.Input.RequestBody, &req)
	result := models.ScanAll(req.Folder)
	c.Data["json"] = result
	c.ServeJSON()
}

// @Title RunBackup
// @Description trigger an immediate node backup across all targets (asynchronous)
// @Success 202 {string} backup started
// @Failure 400 no targets configured
// @router /backup/run [post]
func (c *NodeController) RunBackup() {
	bc := models.GetNodeBackupConfig()
	if len(bc.Targets) == 0 {
		c.Ctx.Output.SetStatus(400)
		c.Data["json"] = map[string]string{"error": "no backup targets configured"}
		c.ServeJSON()
		return
	}
	c.Ctx.Output.SetStatus(202)
	c.Data["json"] = map[string]string{"message": "backup started"}
	c.ServeJSON()
	go func() {
		_ = models.RunNodeBackup(nil)
	}()
}

// @Title GetBackupTargets
// @Description list node backup target definitions
// @Success 200 {object} []models.BackupTarget
// @router /backup/targets [get]
func (c *NodeController) GetBackupTargets() {
	c.Data["json"] = models.GetNodeBackupTargets()
	c.ServeJSON()
}

// @Title AddBackupTarget
// @Description define a new node backup target
// @Param body body models.BackupTarget true "backup target definition"
// @Success 201 {object} models.BackupTarget
// @Failure 400 invalid body
// @router /backup/targets [post]
func (c *NodeController) AddBackupTarget() {
	var t models.BackupTarget
	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &t); err != nil {
		c.Ctx.Output.SetStatus(400)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	created, err := models.AddNodeBackupTarget(t)
	if err != nil {
		c.Ctx.Output.SetStatus(400)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Ctx.Output.SetStatus(201)
	c.Data["json"] = created
	c.ServeJSON()
}

// @Title GetBackupTarget
// @Description get a node backup target by id
// @Param tid path string true "backup target id"
// @Success 200 {object} models.BackupTarget
// @Failure 404 not found
// @router /backup/targets/:tid [get]
func (c *NodeController) GetBackupTarget() {
	tid := c.Ctx.Input.Param(":tid")
	t, err := models.GetNodeBackupTarget(tid)
	if err != nil {
		c.Ctx.Output.SetStatus(404)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Data["json"] = t
	c.ServeJSON()
}

// @Title PutBackupTarget
// @Description update a node backup target definition
// @Param tid path string true "backup target id"
// @Param body body models.BackupTarget true "updated backup target"
// @Success 200 {object} models.BackupTarget
// @Failure 400 invalid body
// @Failure 404 not found
// @router /backup/targets/:tid [put]
func (c *NodeController) PutBackupTarget() {
	tid := c.Ctx.Input.Param(":tid")
	var t models.BackupTarget
	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &t); err != nil {
		c.Ctx.Output.SetStatus(400)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	updated, err := models.UpdateNodeBackupTarget(tid, t)
	if err != nil {
		status := 404
		if err.Error() != "backup target not found" {
			status = 400
		}
		c.Ctx.Output.SetStatus(status)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Data["json"] = updated
	c.ServeJSON()
}

// @Title DeleteBackupTarget
// @Description delete a node backup target definition
// @Param tid path string true "backup target id"
// @Success 200 {string} delete success
// @Failure 404 not found
// @router /backup/targets/:tid [delete]
func (c *NodeController) DeleteBackupTarget() {
	tid := c.Ctx.Input.Param(":tid")
	if err := models.DeleteNodeBackupTarget(tid); err != nil {
		c.Ctx.Output.SetStatus(404)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Data["json"] = map[string]string{"message": "delete success"}
	c.ServeJSON()
}

// @Title RunBackupTarget
// @Description trigger an immediate backup for a single node target (asynchronous)
// @Param tid path string true "backup target id"
// @Success 202 {string} backup started
// @Failure 404 not found
// @router /backup/targets/:tid/run [post]
func (c *NodeController) RunBackupTarget() {
	tid := c.Ctx.Input.Param(":tid")
	if _, err := models.GetNodeBackupTarget(tid); err != nil {
		c.Ctx.Output.SetStatus(404)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	c.Ctx.Output.SetStatus(202)
	c.Data["json"] = map[string]string{"message": "backup started"}
	c.ServeJSON()
	go func() {
		_ = models.RunNodeBackup([]string{tid})
	}()
}

// @Title GetBackupTargetProgress
// @Description get the current/last run state for a node backup target
// @Param tid path string true "backup target id"
// @Success 200 {object} models.RunState
// @Failure 404 not found
// @router /backup/targets/:tid/progress [get]
func (c *NodeController) GetBackupTargetProgress() {
	tid := c.Ctx.Input.Param(":tid")
	if _, err := models.GetNodeBackupTarget(tid); err != nil {
		c.Ctx.Output.SetStatus(404)
		c.Data["json"] = map[string]string{"error": err.Error()}
		c.ServeJSON()
		return
	}
	state, ok := models.GetProgress("node", tid)
	if !ok {
		state = models.RunState{TargetID: tid}
	}
	c.Data["json"] = state
	c.ServeJSON()
}

// @Title GetBackupProgress
// @Description get the current/last run state for every node backup target
// @Success 200 {object} []models.RunState
// @router /backup/progress [get]
func (c *NodeController) GetBackupProgress() {
	c.Data["json"] = models.GetProgressForScope("node")
	c.ServeJSON()
}
