package routers

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"strings"
	"time"

	"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/server/interface/api/srequest"
	"github.com/tech/sendico/server/interface/api/sresponse"
	"github.com/tech/sendico/server/internal/server/confirmationimp"
	"go.uber.org/zap"
)

const pendingLoginTTLMinutes = 10

func (pr *PublicRouter) logUserIn(ctx context.Context, _ *http.Request, req *srequest.Login) http.HandlerFunc {
	// Get the account database entry
	trimmedLogin := strings.TrimSpace(req.Login)
	account, err := pr.db.GetByEmail(ctx, strings.ToLower(trimmedLogin))
	if errors.Is(err, merrors.ErrNoData) || (account == nil) {
		pr.logger.Debug("User not found while logging in", zap.Error(err), zap.String("login", req.Login))
		return response.Unauthorized(pr.logger, pr.service, "user not found")
	}
	if err != nil {
		pr.logger.Warn("Failed to query user with email", zap.Error(err), zap.String("login", req.Login))
		return response.Internal(pr.logger, pr.service, err)
	}

	if account.VerifyToken != "" {
		return response.Forbidden(pr.logger, pr.service, "account_not_verified", "Account verification required")
	}

	if !account.MatchPassword(req.Password) {
		return response.Unauthorized(pr.logger, pr.service, "password does not match")
	}

	pendingToken, err := pr.imp.CreatePendingToken(account, pendingLoginTTLMinutes)
	if err != nil {
		pr.logger.Warn("Failed to generate pending token", zap.Error(err))
		return response.Internal(pr.logger, pr.service, err)
	}

	cfg := confirmationimp.DefaultConfig()
	_, rec, err := pr.cstore.Create(ctx, account.ID, account.Login, model.ConfirmationTargetLogin, cfg, pr.generateCode)
	if err != nil {
		pr.logger.Warn("Failed to create login confirmation code", zap.Error(err))
		return response.Internal(pr.logger, pr.service, err)
	}
	pr.logger.Info("Login confirmation code issued",
		zap.String("destination", pr.maskEmail(account.Login)),
		zap.String("account", account.Login))

	return sresponse.LoginPending(pr.logger, account, &pendingToken, pr.maskEmail(account.Login), int(time.Until(rec.ExpiresAt).Seconds()))
}

func (a *PublicRouter) login(r *http.Request) http.HandlerFunc {
	// TODO: add rate check
	var req srequest.Login
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		a.logger.Info("Failed to decode login request", zap.Error(err))
		return response.BadPayload(a.logger, mservice.Accounts, err)
	}
	req.Login = strings.TrimSpace(req.Login)
	req.Password = strings.TrimSpace(req.Password)
	if req.Login == "" {
		return response.BadRequest(a.logger, mservice.Accounts, "email_missing", "login request has no user name")
	}
	if req.Password == "" {
		return response.BadRequest(a.logger, mservice.Accounts, "password_missing", "login request has no password")
	}
	return a.logUserIn(r.Context(), r, &req)
}
