package auth

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

	"golang.org/x/crypto/bcrypt"

	"github.com/SecurityBrewery/catalyst/app/database/sqlc"
	"github.com/SecurityBrewery/catalyst/app/settings"
)

var ErrUserInactive = errors.New("user is inactive")

func handleLogin(queries *sqlc.Queries) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		type loginData struct {
			Email    string `json:"email"`
			Password string `json:"password"`
		}

		var data loginData
		if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
			unauthorizedJSON(w, "Invalid request")

			return
		}

		user, err := loginWithMail(r.Context(), data.Email, data.Password, queries)
		if err != nil {
			if errors.Is(err, ErrUserInactive) {
				unauthorizedJSON(w, "User is inactive")

				return
			}

			unauthorizedJSON(w, "Login failed")

			return
		}

		permissions, err := queries.ListUserPermissions(r.Context(), user.ID)
		if err != nil {
			errorJSON(w, http.StatusInternalServerError, "Failed to get user permissions")

			return
		}

		settings, err := settings.Load(r.Context(), queries)
		if err != nil {
			errorJSON(w, http.StatusInternalServerError, "Failed to load settings")

			return
		}

		duration := time.Duration(settings.RecordAuthToken.Duration) * time.Second

		token, err := CreateAccessToken(r.Context(), user, permissions, duration, queries)
		if err != nil {
			errorJSON(w, http.StatusInternalServerError, "Failed to create login token")

			return
		}

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)

		response := map[string]string{
			"token": token,
		}

		if err := json.NewEncoder(w).Encode(response); err != nil {
			errorJSON(w, http.StatusInternalServerError, "Failed to encode response")

			return
		}
	}
}

func loginWithMail(ctx context.Context, mail, password string, queries *sqlc.Queries) (*sqlc.User, error) {
	user, err := queries.UserByEmail(ctx, &mail)
	if err != nil {
		return nil, fmt.Errorf("failed to find user by email %q: %w", mail, err)
	}

	if !user.Active {
		return nil, ErrUserInactive
	}

	if err := bcrypt.CompareHashAndPassword([]byte(user.Passwordhash), []byte(password)); err != nil {
		return nil, fmt.Errorf("invalid credentials: %w", err)
	}

	return &user, nil
}
