package routers

import (
	"errors"
	"net/http"

	"github.com/go-chi/jwtauth/v5"
	api "github.com/tech/sendico/pkg/api/http"
	"github.com/tech/sendico/pkg/api/http/response"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	"github.com/tech/sendico/pkg/mutil/mzap"
	"github.com/tech/sendico/server/interface/api/sresponse"
	emodel "github.com/tech/sendico/server/interface/model"
	"go.uber.org/zap"
)

type tokenHandlerFunc = func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc

func (ar *AuthorizedRouter) tokenHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler tokenHandlerFunc) {
	hndlr := func(r *http.Request) http.HandlerFunc {
		_, claims, err := jwtauth.FromContext(r.Context())
		if err != nil {
			ar.logger.Debug("Authorization failed", zap.Error(err), zap.String("request", r.URL.Path))
			return response.Unauthorized(ar.logger, ar.service, "credentials required")
		}
		t, err := emodel.Claims2Token(claims)
		if err != nil {
			ar.logger.Debug("Failed to decode account token", zap.Error(err))
			return response.BadRequest(ar.logger, ar.service, "credentials_unreadable", "faild to parse credentials")
		}
		return handler(r, t)
	}
	ar.imp.InstallHandler(service, endpoint, method, hndlr)
}

func (ar *AuthorizedRouter) AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc) {
	hndlr := func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc {
		if t.Pending {
			return response.Forbidden(ar.logger, ar.service, "confirmation_required", "pending token requires confirmation")
		}
		var a model.Account
		if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
			if errors.Is(err, merrors.ErrNoData) {
				ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.ObjRef("account_ref", t.AccountRef))
				return response.NotFound(ar.logger, ar.service, err.Error())
			}
			return response.Internal(ar.logger, ar.service, err)
		}
		accessToken, err := ar.imp.CreateAccessToken(&a)
		if err != nil {
			ar.logger.Warn("Failed to generate access token", zap.Error(err))
			return response.Internal(ar.logger, ar.service, err)
		}
		return handler(r, &a, &accessToken)
	}
	ar.tokenHandler(service, endpoint, method, hndlr)
}

func (ar *AuthorizedRouter) PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc) {
	hndlr := func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc {
		var a model.Account
		if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
			if errors.Is(err, merrors.ErrNoData) {
				ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.ObjRef("account_ref", t.AccountRef))
				return response.NotFound(ar.logger, ar.service, err.Error())
			}
			return response.Internal(ar.logger, ar.service, err)
		}
		return handler(r, &a, t)
	}
	ar.tokenHandler(service, endpoint, method, hndlr)
}
