package resource

import (
	"context"
	"fmt"
	"net/http"
	"strconv"

	"github.com/aarondl/opt/omit"
	"github.com/aarondl/opt/omitnull"
	"github.com/google/uuid"
	"github.com/gorilla/mux"
	"source.gleipnir.technology/Gleipnir/nidus-sync/db/enums"
	"source.gleipnir.technology/Gleipnir/nidus-sync/db/models"
	nhttp "source.gleipnir.technology/Gleipnir/nidus-sync/http"
	"source.gleipnir.technology/Gleipnir/nidus-sync/platform"
	//"github.com/rs/zerolog/log"
)

type user struct {
	Avatar           omitnull.Val[string] `json:"avatar"`
	DisplayName      omit.Val[string]     `json:"display_name"`
	ID               omit.Val[int]        `json:"id"`
	Initials         omit.Val[string]     `json:"initials"`
	IsActive         omit.Val[bool]       `json:"is_active"`
	PasswordHash     omit.Val[string]     `json:"-"`
	PasswordHashType omit.Val[string]     `json:"-"`
	Role             omit.Val[string]     `json:"role"`
	Tags             omit.Val[[]string]   `json:"tags"`
	URI              omit.Val[string]     `json:"uri"`
	Username         omit.Val[string]     `json:"username"`
}

func User(r *router) *userR {
	return &userR{
		router: r,
	}
}
func (res *userR) response(u *platform.User) (*user, error) {
	if u == nil {
		return nil, fmt.Errorf("nil user")
	}
	avatar, err := res.router.UUIDToURI("avatar.ByUUIDGet", u.Avatar)
	if err != nil {
		return nil, fmt.Errorf("id to uri: %w", err)
	}
	uri, err := res.router.IDToURI("user.ByIDGet", u.ID)
	if err != nil {
		return nil, fmt.Errorf("id to uri: %w", err)
	}
	tags := make([]string, 0)
	if u.IsDronePilot {
		tags = append(tags, "drone pilot")
	}
	if u.IsWarrant {
		tags = append(tags, "warrant")
	}
	return &user{
		Avatar:      omitnull.FromPtr(avatar),
		DisplayName: omit.From(u.DisplayName),
		ID:          omit.From(int(u.ID)),
		Initials:    omit.From(u.Initials),
		IsActive:    omit.From(u.Active),
		Role:        omit.From(u.Role),
		Tags:        omit.From(tags),
		URI:         omit.From(uri),
		Username:    omit.From(u.Username),
	}, nil
}

type userR struct {
	router *router
}

func (res *userR) ByIDGet(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*platform.User, *nhttp.ErrorWithStatus) {
	vars := mux.Vars(r)
	user_id_str := vars["id"]
	user_id, err := strconv.Atoi(user_id_str)
	u, err := platform.UserByID(ctx, int32(user_id))
	if err != nil {
		return nil, nhttp.NewError("get user: %w", err)
	}
	return u, nil
}

func (res *userR) ByIDPut(ctx context.Context, r *http.Request, user platform.User, updates user) (string, *nhttp.ErrorWithStatus) {
	vars := mux.Vars(r)
	user_id_str := vars["id"]
	user_id, err := strconv.Atoi(user_id_str)
	if err != nil {
		return "", nhttp.NewErrorStatus(http.StatusBadRequest, "user id conversion: %w", err)
	}
	user_changes := &models.UserSetter{}
	if !user.HasRoot() && !user.IsAccountOwner() && user.ID != user_id {
		return "", nhttp.NewForbidden("Only account owners can change other users")
	}
	if updates.Avatar.IsValue() {
		avatar_uuid, err := res.router.UUIDFromURI("avatar.ByUUIDGet", updates.Avatar.MustGet())
		if err != nil {
			return "", nhttp.NewBadRequest("parse avatar uri: %w", err)
		}
		user_changes.Avatar = omitnull.FromPtr(avatar_uuid)
	} else if updates.Avatar.IsNull() {
		user_changes.Avatar = omitnull.FromPtr[uuid.UUID](nil)
	}
	if updates.DisplayName.IsValue() {
		user_changes.DisplayName = updates.DisplayName
	}
	if updates.Role.IsValue() {
		// Don't allow privilege escalation
		if user.HasRoot() || user.IsAccountOwner() {
			var role enums.Userrole
			v := updates.Role.MustGet()
			err := role.Scan(v)
			if err != nil {
				return "", nhttp.NewBadRequest("invalid role %s: %w", v, err)
			}
			user_changes.Role = omit.From(role)
		} else {
			return "", nhttp.NewBadRequest("you aren't allowed to change roles")
		}
	}
	if updates.Tags.IsValue() {
		for i, v := range updates.Tags.MustGet() {
			user_changes.IsDronePilot = omit.From(false)
			user_changes.IsWarrant = omit.From(false)
			switch v {
			case "drone pilot":
				user_changes.IsDronePilot = omit.From(true)
			case "warrant":
				user_changes.IsWarrant = omit.From(true)
			default:
				return "", nhttp.NewBadRequest("'%s' (item %d) is not a valid tag", v, i)
			}
		}
	}

	err = platform.UserUpdate(ctx, user, user_id, user_changes)
	if err != nil {
		return "", nhttp.NewError("user update: %w", err)
	}
	return "", nil
}

func (res *userR) SelfGet(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*user, *nhttp.ErrorWithStatus) {
	resp, err := res.response(&user)
	if err != nil {
		return nil, nhttp.NewError("create response: %w", err)
	}
	return resp, nil
}

func (res *userR) List(ctx context.Context, r *http.Request, u platform.User, query QueryParams) ([]*user, *nhttp.ErrorWithStatus) {
	users, err := platform.UserList(ctx, u)
	if err != nil {
		return nil, nhttp.NewError("list users: %w", err)
	}
	results := make([]*user, len(users))
	//log.Debug().Int("len", len(users)).Msg("building response")
	for i, v := range users {
		//log.Debug().Int("i", i).Msg("making results")
		resp, err := res.response(v)
		if err != nil {
			return nil, nhttp.NewError("create response: %w", err)
		}
		results[i] = resp
	}
	return results, nil
}

type responseListUserSuggestion struct {
	Users []*platform.User `json:"users"`
}

func (res *userR) SuggestionGet(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*responseListUserSuggestion, *nhttp.ErrorWithStatus) {
	if query.Query == nil {
		return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "you need to include a query")
	}
	users, err := platform.UserSuggestion(ctx, user, *query.Query)
	if err != nil {
		return nil, nhttp.NewError("query suggestions: %w", err)
	}
	return &responseListUserSuggestion{
		Users: users,
	}, nil
}
