package web

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

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

func (web *Web) IssuesPage(w http.ResponseWriter, r *http.Request) {
	user := common.GetUser(r)
	if user.IsAnonymous() {
		http.Redirect(w, r, "/", http.StatusSeeOther)
		return
	}

	issues, _, err := web.App.FetchIssues("", data.Filters{
		Page:         1,
		PageSize:     50,
		Sort:         "-id",
		SortSafelist: []string{"id", "-id", "title", "-title"},
	}, user)
	if err != nil {
		web.App.LogError(r, err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	web.render(w, r, http.StatusOK, "issues", struct {
		AuthenticatedUser *data.User
		Issues            []common.IssueDetail
	}{
		AuthenticatedUser: user,
		Issues:            issues,
	})
}

func (web *Web) CreateIssueAction(w http.ResponseWriter, r *http.Request) {
	user := common.GetUser(r)
	if user.IsAnonymous() {
		http.Redirect(w, r, "/", http.StatusSeeOther)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	startTime, err := time.Parse("2006-01-02 15:04", r.PostFormValue("start_date")+" "+r.PostFormValue("start_clock"))
	if err != nil {
		startTime = time.Time{}
	}
	endTime, err := time.Parse("2006-01-02 15:04", r.PostFormValue("end_date")+" "+r.PostFormValue("end_clock"))
	if err != nil {
		endTime = time.Time{}
	}

	var options []string
	for _, v := range r.PostForm["options"] {
		if trimmed := strings.TrimSpace(v); trimmed != "" {
			options = append(options, trimmed)
		}
	}

	issue, _, err := web.App.CreateIssue(
		r.PostFormValue("title"),
		r.PostFormValue("description"),
		startTime,
		endTime,
		options,
	)
	if err != nil {
		web.App.LogError(r, err)
		http.Redirect(w, r, "/issues", http.StatusSeeOther)
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/issues/%d", issue.ID), http.StatusSeeOther)
}

func (web *Web) IssuePage(w http.ResponseWriter, r *http.Request) {
	user := common.GetUser(r)
	if user.IsAnonymous() {
		http.Redirect(w, r, "/", http.StatusSeeOther)
		return
	}

	id, err := common.ReadIDParam(r)
	if err != nil {
		http.NotFound(w, r)
		web.errorResponse(w, r, data.ErrRecordNotFound)
		return
	}

	result, err := web.App.GetIssue(id, user)
	if err != nil {
		if errors.Is(err, data.ErrRecordNotFound) {
			http.NotFound(w, r)
		} else {
			web.App.LogError(r, err)
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		}
		return
	}

	web.render(w, r, http.StatusOK, "issue", struct {
		AuthenticatedUser *data.User
		Issue             *common.IssueWithOptions
	}{
		AuthenticatedUser: user,
		Issue:             result,
	})
}

func (web *Web) UpdateIssueAction(w http.ResponseWriter, r *http.Request) {
	if common.GetUser(r).IsAnonymous() {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	id, err := common.ReadIDParam(r)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if err = r.ParseForm(); err != nil {
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	var title, description *string
	var startTime, endTime *time.Time

	if v := r.FormValue("title"); v != "" {
		title = &v
	}
	if v := r.FormValue("description"); v != "" {
		description = &v
	}
	if d, c := r.FormValue("start_date"), r.FormValue("start_clock"); d != "" && c != "" {
		if t, parseErr := time.Parse("2006-01-02 15:04", d+" "+c); parseErr == nil {
			startTime = &t
		}
	}
	if d, c := r.FormValue("end_date"), r.FormValue("end_clock"); d != "" && c != "" {
		if t, parseErr := time.Parse("2006-01-02 15:04", d+" "+c); parseErr == nil {
			endTime = &t
		}
	}

	if _, err = web.App.UpdateIssue(id, title, description, startTime, endTime); err != nil {
		web.App.LogError(r, err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("HX-Redirect", fmt.Sprintf("/issues/%d", id))
	w.WriteHeader(http.StatusOK)
}

func (web *Web) DeleteIssueAction(w http.ResponseWriter, r *http.Request) {
	if common.GetUser(r).IsAnonymous() {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	id, err := common.ReadIDParam(r)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if err = web.App.DeleteIssue(id); err != nil {
		if errors.Is(err, data.ErrRecordNotFound) {
			http.NotFound(w, r)
		} else {
			web.App.LogError(r, err)
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		}
		return
	}

	w.Header().Set("HX-Redirect", "/issues")
	w.WriteHeader(http.StatusOK)
}

func (web *Web) GetIssuePubKey(w http.ResponseWriter, r *http.Request) {
	id, err := common.ReadIDParam(r)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	pubKey, err := web.App.GetIssuePublicKey(id)
	if err != nil {
		if errors.Is(err, data.ErrRecordNotFound) {
			http.NotFound(w, r)
		} else {
			web.App.LogError(r, err)
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		}
		return
	}

	if err = common.WriteJSON(w, http.StatusOK, common.Envelope{"public_key": pubKey}, nil); err != nil {
		web.App.LogError(r, err)
	}
}

func (web *Web) BlindSignIssueVote(w http.ResponseWriter, r *http.Request) {
	user := common.GetUser(r)
	if user.IsAnonymous() {
		common.WriteJSON(w, http.StatusUnauthorized, common.Envelope{"error": map[string]string{"message": "authentication required"}}, nil)
		return
	}

	id, err := common.ReadIDParam(r)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	var input struct {
		BlindedVote []byte `json:"blinded_vote"`
	}
	if err = common.ReadJSON(w, r, &input); err != nil {
		common.WriteJSON(w, http.StatusBadRequest, common.Envelope{"error": map[string]string{"message": err.Error()}}, nil)
		return
	}

	signed, err := web.App.BlindSign(id, input.BlindedVote, user)
	if err != nil {
		switch {
		case errors.Is(err, data.ErrRecordNotFound):
			http.NotFound(w, r)
		case errors.Is(err, data.ErrDuplicateBlindSign):
			common.WriteJSON(w, http.StatusConflict, common.Envelope{"error": map[string]string{"message": err.Error()}}, nil)
		case errors.Is(err, data.ErrInvalidBlindedVote):
			common.WriteJSON(w, http.StatusUnprocessableEntity, common.Envelope{"error": map[string]string{"message": err.Error()}}, nil)
		case errors.Is(err, data.ErrHasNotStarted):
			common.WriteJSON(w, http.StatusUnprocessableEntity, common.Envelope{"error": map[string]string{"message": err.Error()}}, nil)
		default:
			web.App.LogError(r, err)
			common.WriteJSON(w, http.StatusInternalServerError, common.Envelope{"error": map[string]string{"message": "internal server error"}}, nil)
		}
		return
	}

	if err = common.WriteJSON(w, http.StatusOK, common.Envelope{"signed": signed}, nil); err != nil {
		web.ServerErrorResponse(w, r, err)
	}
}
