package hosts

import (
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/gofiber/fiber/v2"
	"rul.sh/vaulterm/server/lib"
	"rul.sh/vaulterm/server/models"
	"rul.sh/vaulterm/server/utils"
)

func Router(app fiber.Router) {
	router := app.Group("/hosts")

	router.Get("/", getAll)
	router.Post("/", create)
	router.Put("/:id", update)
	router.Delete("/:id", delete)
	router.Post("/move", move)
	router.Get("/tags", getTags)
}

func getAll(c *fiber.Ctx) error {
	teamId := c.Query("teamId")
	parentId := c.Query("parentId")

	user := lib.GetUser(c)
	repo := NewRepository(&Hosts{User: user})

	if teamId != "" && !user.IsInTeam(&teamId) {
		return utils.ResponseError(c, errors.New("no access"), 403)
	}

	rows, err := repo.GetAll(GetAllOpt{TeamID: teamId, ParentID: &parentId})
	if err != nil {
		return utils.ResponseError(c, err, 500)
	}

	return c.JSON(fiber.Map{
		"rows": rows,
	})
}

func create(c *fiber.Ctx) error {
	var body CreateHostSchema
	if err := c.BodyParser(&body); err != nil {
		return utils.ResponseError(c, err, 500)
	}

	user := lib.GetUser(c)
	repo := NewRepository(&Hosts{User: user})

	if body.TeamID != nil && !user.TeamCanWrite(body.TeamID) {
		return utils.ResponseError(c, errors.New("no access"), 403)
	}

	item := &models.Host{
		OwnerID:  &user.ID,
		TeamID:   body.TeamID,
		Type:     body.Type,
		Label:    body.Label,
		Host:     body.Host,
		Port:     body.Port,
		Metadata: body.Metadata,
		ParentID: body.ParentID,
		KeyID:    body.KeyID,
		AltKeyID: body.AltKeyID,
	}

	item.Tags = []*models.HostTag{}
	for _, tag := range body.Tags {
		item.Tags = append(item.Tags, &models.HostTag{Name: tag})
	}

	osName, err := tryConnect(c, item)
	if err != nil {
		return utils.ResponseError(c, fmt.Errorf("cannot connect to the host: %s", err), 500)
	}
	item.OS = osName

	if err := repo.Create(item); err != nil {
		return utils.ResponseError(c, err, 500)
	}

	return c.Status(http.StatusCreated).JSON(item)
}

func update(c *fiber.Ctx) error {
	var body CreateHostSchema
	if err := c.BodyParser(&body); err != nil {
		return utils.ResponseError(c, err, 500)
	}

	user := lib.GetUser(c)
	repo := NewRepository(&Hosts{User: user})

	id := c.Params("id")
	data, _ := repo.Get(id)
	if data == nil {
		return utils.ResponseError(c, errors.New("host not found"), 404)
	}
	if !data.CanWrite(&user.User) || !user.TeamCanWrite(body.TeamID) {
		return utils.ResponseError(c, errors.New("no access"), 403)
	}

	item := &models.Host{
		Model:    models.Model{ID: id},
		TeamID:   body.TeamID,
		Type:     body.Type,
		Label:    body.Label,
		Host:     body.Host,
		Port:     body.Port,
		Metadata: body.Metadata,
		ParentID: body.ParentID,
		KeyID:    body.KeyID,
		AltKeyID: body.AltKeyID,
	}

	// osName, err := tryConnect(c, item)
	// if err != nil {
	// 	return utils.ResponseError(c, fmt.Errorf("cannot connect to the host: %s", err), 500)
	// }
	// item.OS = osName

	if err := repo.Update(id, item); err != nil {
		return utils.ResponseError(c, err, 500)
	}

	tags := []models.HostTag{}
	for _, tag := range body.Tags {
		tags = append(tags, models.HostTag{
			Name: tag,
		})
	}

	if err := repo.db.Unscoped().Model(&item).
		Association("Tags").Unscoped().Replace(&tags); err != nil {
		return utils.ResponseError(c, err, 500)
	}

	return c.JSON(item)
}

func delete(c *fiber.Ctx) error {
	user := lib.GetUser(c)
	repo := NewRepository(&Hosts{User: user})

	id := c.Params("id")
	host, _ := repo.Get(id)
	if host == nil {
		return utils.ResponseError(c, errors.New("host not found"), 404)
	}
	if !host.CanWrite(&user.User) {
		return utils.ResponseError(c, errors.New("no access"), 403)
	}

	if err := repo.Delete(id); err != nil {
		return utils.ResponseError(c, err, 500)
	}

	return c.JSON(fiber.Map{
		"status":  "ok",
		"message": "Successfully deleted",
	})
}

func move(c *fiber.Ctx) error {
	user := lib.GetUser(c)
	repo := NewRepository(&Hosts{User: user})

	// validate request
	var body MoveHostSchema
	if err := c.BodyParser(&body); err != nil {
		return utils.ResponseError(c, err, 500)
	}
	if body.HostID == "" {
		return utils.ResponseError(c, errors.New("invalid request"), 400)
	}

	// get parent
	var parentId *string

	if body.ParentID != "" {
		parent, err := repo.Get(body.ParentID)
		if err != nil {
			return utils.ResponseError(c, err, 500)
		}
		if !parent.CanWrite(&user.User) {
			return utils.ResponseError(c, errors.New("no access"), 403)
		}
		parentId = &body.ParentID
	}

	// get hosts
	hostIds := strings.Split(body.HostID, ",")
	hosts, err := repo.GetAll(GetAllOpt{TeamID: body.TeamID, ID: hostIds})
	if err != nil {
		return utils.ResponseError(c, err, 500)
	}

	if len(hosts) != len(hostIds) {
		return utils.ResponseError(c, errors.New("one or more hosts not found"), 400)
	}

	for _, host := range hosts {
		if !host.CanWrite(&user.User) {
			return utils.ResponseError(c, errors.New("no access"), 403)
		}
	}

	// move the hosts to new parent
	if err := repo.SetParentId(parentId, hostIds); err != nil {
		return utils.ResponseError(c, err, 500)
	}

	return c.JSON(true)
}

func getTags(c *fiber.Ctx) error {
	teamId := c.Query("teamId")
	user := lib.GetUser(c)
	repo := NewRepository(&Hosts{User: user})

	rows, err := repo.GetAvailableTags(teamId)
	if err != nil {
		return utils.ResponseError(c, err, 500)
	}

	return c.JSON(fiber.Map{
		"rows": rows,
	})
}