package gapi

import (
	"context"
	"database/sql"
	"errors"
	"log/slog"
	"time"

	"github.com/itsscb/df/bff/pb"
	"github.com/itsscb/df/bff/val"
	"google.golang.org/genproto/googleapis/rpc/errdetails"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func (server *Server) RefreshToken(ctx context.Context, req *pb.RefreshTokenRequest) (*pb.RefreshTokenResponse, error) {
	violations := validateRefreshTokenRequest(req)
	if violations != nil {
		return nil, invalidArgumentError(violations)
	}

	refreshPayload, err := server.tokenMaker.VerifyToken(req.GetRefreshToken())
	if err != nil {
		return nil, status.Error(codes.PermissionDenied, "invalid session token")

	}

	session, err := server.store.GetSession(ctx, refreshPayload.ID)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, status.Error(codes.NotFound, "session not found")
		}
		slog.Error("refresh_token (get_account)", slog.Int64("invoked_by", int64(refreshPayload.AccountID)), slog.String("refresh_token", req.GetRefreshToken()), slog.String("error", err.Error()))
		return nil, status.Error(codes.Internal, "cannot find session")
	}

	if session.IsBlocked {
		return nil, status.Error(codes.PermissionDenied, "session is blocked")
	}

	if session.AccountID != refreshPayload.AccountID {
		return nil, status.Error(codes.PermissionDenied, "invalid account session")

	}

	if session.RefreshToken != req.RefreshToken {
		return nil, status.Error(codes.PermissionDenied, "mismatched session token")

	}

	if time.Now().After(session.ExpiresAt) {
		return nil, status.Error(codes.PermissionDenied, "session expired")
	}

	id, err := server.tokenMaker.NewTokenID()
	if err != nil {
		slog.Error("refresh_token (token_id)", slog.Int64("invoked_by", int64(refreshPayload.AccountID)), slog.String("error", err.Error()))
		return nil, status.Error(codes.Internal, "failed to create session token")
	}
	accessToken, accessPayload, err := server.tokenMaker.CreateToken(
		refreshPayload.AccountID,
		id,
		server.config.AccessTokenDuration,
	)
	if err != nil {
		slog.Error("refresh_token (access_token)", slog.Int64("invoked_by", int64(refreshPayload.AccountID)), slog.String("error", err.Error()))
		return nil, status.Error(codes.Internal, "failed to create session token")
	}

	rsp := &pb.RefreshTokenResponse{
		AccessToken:          accessToken,
		AccessTokenExpiresAt: timestamppb.New(accessPayload.ExpiredAt),
	}
	return rsp, nil
}

func validateRefreshTokenRequest(req *pb.RefreshTokenRequest) (violations []*errdetails.BadRequest_FieldViolation) {
	if err := val.ValidateString(req.GetRefreshToken(), 200, 400); err != nil {
		violations = append(violations, fieldViolation("refresh_token", err))
	}

	return violations
}
