package auth

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"net/http"
	"time"

	"git.optclblast.xyz/draincloud/draincloud-core/internal/common"
	"git.optclblast.xyz/draincloud/draincloud-core/internal/errs"
	"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
	"git.optclblast.xyz/draincloud/draincloud-core/internal/storage"
	models "git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models/auth"
)

const (
	AuthResolverV1Name = "auth.v1"
)

const (
	csrfTokenCookie    = "__Csrf_token"
	sessionTokenCookie = "__Session_token"
)

type AuthResolver struct {
	authStorage storage.AuthStorage
}

func NewAuthResolver(authStorage storage.AuthStorage) *AuthResolver {
	return &AuthResolver{
		authStorage: authStorage,
	}
}

func (r *AuthResolver) Resolve(ctx context.Context, req *common.Request, _ any) error {
	return r.authorize(ctx, req)
}

func (r *AuthResolver) GetRequiredResolveParams() []string {
	return nil
}

func (p *AuthResolver) authorize(ctx context.Context, req *common.Request) error {
	session, err := p.getSession(ctx, req)
	if err != nil && !errors.Is(err, http.ErrNoCookie) {
		return errs.ErrorUnauthorized
	}

	if session == nil {
		return errs.ErrorUnauthorized
	}

	if err := validateSession(ctx, req, session); err != nil {
		// TODO add audit log entry
		return errs.ErrorUnauthorized
	}

	user, err := p.authStorage.GetUserByID(ctx, session.UserID)
	if err != nil {
		return fmt.Errorf("failed to fetch user by id: %w", err)
	}
	logger.Debug(ctx, "[authorize] user authorized", slog.String("session_id", session.ID.String()))

	req.User = user
	req.Session = session

	return nil
}

func (d *AuthResolver) getSession(ctx context.Context, req *common.Request) (*models.Session, error) {
	token, err := common.GetValue[string](req.Metadata, sessionTokenCookie)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch session cookie from request: %w", err)
	}

	if len(token) == 0 {
		return nil, fmt.Errorf("session token or csrf token is empty")
	}

	session, err := d.authStorage.GetSession(ctx, token)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch session from repo: %w", err)
	}

	return session, nil
}

func validateSession(_ context.Context, req *common.Request, session *models.Session) error {
	if session == nil {
		return errs.ErrorAccessDenied
	}

	csrfToken, err := common.GetValue[string](req.Metadata, csrfTokenCookie)
	if err != nil {
		return fmt.Errorf("failed to fetch csrf cookie from request: %w", err)
	}

	if session.CsrfToken != csrfToken {
		return errs.ErrorAccessDenied
	}

	if session.ExpiredAt.Before(time.Now()) {
		return errs.ErrorSessionExpired
	}

	return nil
}
