package api

import (
	"errors"
	"net/http"
	"strconv"
	"time"

	"github.com/julienschmidt/httprouter"
	"party.at/party/cmd/party/common"
	"party.at/party/internal/data"
	"party.at/party/internal/validator"
)

func (api *Api) ListUsers(w http.ResponseWriter, r *http.Request) {
	var input struct {
		data.Filters
	}

	v := validator.New()
	qs := r.URL.Query()

	input.Filters.Page = api.readInt(qs, "page", 1, v)
	input.Filters.PageSize = api.readInt(qs, "page_size", 20, v)
	input.Filters.Sort = api.readString(qs, "sort", "id")
	input.Filters.SortSafelist = []string{"id", "-id", "name", "-name", "email", "-email", "created", "-created"}

	if data.ValidateFilters(v, input.Filters); !v.Valid() {
		api.FailedValidationResponse(w, r, v.Errors)
		return
	}

	users, metadata, err := api.App.ListUsers(input.Filters)
	if err != nil {
		api.ServerErrorResponse(w, r, err)
		return
	}

	if err = api.writeJSON(w, http.StatusOK, envelope{"users": users, "metadata": metadata}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

func (api *Api) CreateUser(w http.ResponseWriter, r *http.Request) {
	var input struct {
		ProviderId  int64     `json:"provider_id"`
		Username    string    `json:"username"`
		PhoneNumber string    `json:"phone_number"`
		Country     string    `json:"country"`
		Email       string    `json:"email"`
		Password    string    `json:"password"`
		Name        string    `json:"name"`
		AltName     *string   `json:"alt_name"`
		DateOfBirth time.Time `json:"date_of_birth"`
		Address     string    `json:"address"`
	}

	if err := api.readJSON(w, r, &input); err != nil {
		api.BadRequestResponse(w, r, err)
		return
	}

	user, authToken, err := api.App.RegisterUser(common.RegisterUserInput{
		ProviderID:  input.ProviderId,
		Username:    input.Username,
		PhoneNumber: input.PhoneNumber,
		Country:     input.Country,
		Email:       input.Email,
		Password:    input.Password,
		Name:        input.Name,
		AltName:     input.AltName,
		DateOfBirth: input.DateOfBirth,
		Address:     input.Address,
	})
	if err != nil {
		var ve *common.ValidationError
		switch {
		case errors.As(err, &ve):
			api.FailedValidationResponse(w, r, ve.Errors)
		case errors.Is(err, data.ErrDuplicateEmail):
			v := validator.New()
			v.AddError("email", "a user with this email address already exists")
			api.FailedValidationResponse(w, r, v.Errors)
		case errors.Is(err, data.ErrDuplicateUser):
			v := validator.New()
			v.AddError("username", "a user with this username already exists")
			api.FailedValidationResponse(w, r, v.Errors)
		default:
			api.ServerErrorResponse(w, r, err)
		}
		return
	}

	if err = api.writeJSON(w, http.StatusCreated, envelope{"user": user, "authentication_token": authToken}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

func (api *Api) ReadUser(w http.ResponseWriter, r *http.Request) {
	var id int64
	if param := httprouter.ParamsFromContext(r.Context()).ByName("id"); param == "me" {
		id = GetUser(r).ID
	} else {
		var err error
		id, err = strconv.ParseInt(param, 10, 64)
		if err != nil || id < 1 {
			api.NotFoundResponse(w, r)
			return
		}
	}

	if GetUser(r).ID != id {
		api.errorResponse(w, r, http.StatusForbidden, apiError{
			Code:    common.ErrCodeNotPermitted,
			Message: "your user account doesn't have the necessary permissions to access this resource",
		})
		return
	}

	user, err := api.App.GetUser(id)
	if err != nil {
		if errors.Is(err, data.ErrRecordNotFound) {
			api.NotFoundResponse(w, r)
		} else {
			api.ServerErrorResponse(w, r, err)
		}
		return
	}

	if err = api.writeJSON(w, http.StatusOK, envelope{"user": user}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

func (api *Api) ReadAuthenticatedUser(w http.ResponseWriter, r *http.Request) {
	user, err := api.App.GetUser(GetUser(r).ID)
	if err != nil {
		if errors.Is(err, data.ErrRecordNotFound) {
			api.NotFoundResponse(w, r)
		} else {
			api.ServerErrorResponse(w, r, err)
		}
		return
	}

	if err = api.writeJSON(w, http.StatusOK, envelope{"user": user}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

func (api *Api) DeleteUser(w http.ResponseWriter, r *http.Request) {
	id, err := api.readIDParam(r)
	if err != nil {
		api.NotFoundResponse(w, r)
		return
	}

	if err = api.App.DeleteUser(id); err != nil {
		if errors.Is(err, data.ErrRecordNotFound) {
			api.NotFoundResponse(w, r)
		} else {
			api.ServerErrorResponse(w, r, err)
		}
		return
	}

	if err = api.writeJSON(w, http.StatusOK, envelope{"message": "user successfully deleted"}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

func (api *Api) ActivateUser(w http.ResponseWriter, r *http.Request) {
	var input struct {
		TokenPlaintext string `json:"token"`
	}

	if err := api.readJSON(w, r, &input); err != nil {
		api.BadRequestResponse(w, r, err)
		return
	}

	v := validator.New()
	if data.ValidateTokenPlaintext(v, input.TokenPlaintext); !v.Valid() {
		api.FailedValidationResponse(w, r, v.Errors)
		return
	}

	user, err := api.App.ActivateUser(input.TokenPlaintext)
	if err != nil {
		switch {
		case errors.Is(err, data.ErrRecordNotFound):
			v.AddError("token", "invalid or expired activation token")
			api.FailedValidationResponse(w, r, v.Errors)
		case errors.Is(err, data.ErrEditConflict):
			api.EditConflictResponse(w, r)
		default:
			api.ServerErrorResponse(w, r, err)
		}
		return
	}

	if err = api.writeJSON(w, http.StatusOK, envelope{"user": user}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}
