package routers

import (
	"context"
	"errors"
	"net/http"
	"time"

	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mutil/mzap"
	"github.com/tech/sendico/server/interface/api/srequest"
	"github.com/tech/sendico/server/interface/api/sresponse"
	"go.uber.org/zap"
)

var errClientIPNotAllowed = errors.New("client_ip_not_allowed")

func validateToken(token string, rt *model.RefreshToken) string {
	if rt.AccountRef == nil {
		return "missing account reference"
	}
	if token != rt.RefreshToken {
		return "tokens do not match"
	}
	if rt.ExpiresAt.Before(time.Now()) {
		return "token expired"
	}
	if rt.IsRevoked {
		return "token has been revoked"
	}

	return ""
}

func (pr *PublicRouter) validateRefreshToken(ctx context.Context, r *http.Request, req *srequest.TokenRefreshRotate) (*model.Account, *sresponse.TokenData, error) {
	client, err := pr.rtdb.GetClient(ctx, req.ClientID)
	if errors.Is(err, merrors.ErrNoData) || client == nil {
		pr.logger.Info("Refresh token rejected: client not found", zap.String("client_id", req.ClientID))
		return nil, nil, merrors.Unauthorized("client not found")
	}
	if err != nil {
		pr.logger.Warn("Failed to fetch client for refresh token validation", zap.Error(err), zap.String("client_id", req.ClientID))
		return nil, nil, err
	}
	if client.IsRevoked {
		return nil, nil, merrors.Unauthorized("client has been revoked")
	}
	if h := pr.validateClientIPPolicy(r, req.ClientID, client); h != nil {
		return nil, nil, errClientIPNotAllowed
	}

	rt, err := pr.rtdb.GetByCRT(ctx, req)
	if errors.Is(err, merrors.ErrNoData) {
		pr.logger.Info("Refresh token not found", zap.String("client_id", req.ClientID), zap.String("device_id", req.DeviceID))
		return nil, nil, err
	}

	if reason := validateToken(req.RefreshToken, rt); len(reason) > 0 {
		pr.logger.Info("Token validation failed", zap.String("reason", reason))
		return nil, nil, merrors.Unauthorized(reason)
	}

	var account model.Account
	if err := pr.db.Get(ctx, *rt.AccountRef, &account); errors.Is(err, merrors.ErrNoData) {
		pr.logger.Info("User not found while rotating refresh token", zap.Error(err), mzap.AccRef(*rt.AccountRef))
		return nil, nil, merrors.Unauthorized("user not found")
	}

	accessToken, err := pr.imp.CreateAccessTokenForClient(&account, req.ClientID)
	if err != nil {
		pr.logger.Warn("Failed to generate access token", zap.Error(err))
		return nil, nil, err
	}

	return &account, &accessToken, nil
}
