package api

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

	"party.at/party/cmd/party/common"
	"party.at/party/internal/data"
	"party.at/party/internal/validator"
)

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

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

	input.Title = common.ReadString(qs, "title", "")
	input.Filters.Page = common.ReadInt(qs, "page", 1, v)
	input.Filters.PageSize = common.ReadInt(qs, "page_size", 20, v)
	input.Filters.Sort = common.ReadString(qs, "sort", "id")
	input.Filters.SortSafelist = []string{"id", "-id", "title", "-title", "description", "-description"}

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

	issues, metadata, err := api.App.FetchIssues(input.Title, input.Filters, GetUser(r))
	if err != nil {
		api.ServerErrorResponse(w, r, err)
		return
	}

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

func (api *Api) CreateIssue(w http.ResponseWriter, r *http.Request) {
	var input struct {
		Title       string    `json:"title"`
		Description string    `json:"description"`
		StartTime   time.Time `json:"start_time"`
		EndTime     time.Time `json:"end_time"`
		Options     []string  `json:"options"`
	}

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

	issue, options, err := api.App.CreateIssue(input.Title, input.Description, input.StartTime, input.EndTime, input.Options)
	if err != nil {
		var ve *common.ValidationError
		if errors.As(err, &ve) {
			api.FailedValidationResponse(w, r, ve.Errors)
		} else {
			api.ServerErrorResponse(w, r, err)
		}
		return
	}

	headers := make(http.Header)
	headers.Set("Location", fmt.Sprintf("/v1/issues/%d", issue.ID))
	if err = api.writeJSON(w, http.StatusCreated, envelope{"issue": issue, "options": options}, headers); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

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

	result, err := api.App.GetIssue(id, GetUser(r))
	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{"issue": result.IssueDetail, "options": result.Options}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

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

	var input struct {
		Title       *string    `json:"title"`
		Description *string    `json:"description"`
		StartTime   *time.Time `json:"start_time"`
		EndTime     *time.Time `json:"end_time"`
	}

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

	issue, err := api.App.UpdateIssue(id, input.Title, input.Description, input.StartTime, input.EndTime)
	if err != nil {
		var ve *common.ValidationError
		switch {
		case errors.As(err, &ve):
			api.FailedValidationResponse(w, r, ve.Errors)
		case errors.Is(err, data.ErrRecordNotFound):
			api.NotFoundResponse(w, r)
		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{"issue": issue}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

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

	if err = api.App.DeleteIssue(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": "issue successfully deleted"}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

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

	pubKey, err := api.App.GetIssuePublicKey(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{"public_key": pubKey}, nil); err != nil {
		api.ServerErrorResponse(w, r, err)
	}
}

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

	var input struct {
		BlindedVote []byte `json:"blinded_vote"`
	}
	if err = api.readJSON(w, r, &input); err != nil {
		api.BadRequestResponse(w, r, err)
		return
	}

	signed, err := api.App.BlindSign(id, input.BlindedVote, GetUser(r))
	if err != nil {
		switch {
		case errors.Is(err, data.ErrRecordNotFound):
			api.NotFoundResponse(w, r)
		case errors.Is(err, data.ErrDuplicateBlindSign):
			api.AlreadyBlindSignedResponse(w, r)
		case errors.Is(err, data.ErrInvalidBlindedVote):
			api.BlindedVoteOutOfRangeResponse(w, r)
		default:
			api.ServerErrorResponse(w, r, err)
		}
		return
	}

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