package routers

import (
	"errors"
	"net/http"
	"strings"

	"github.com/go-chi/jwtauth/v5"
	api "github.com/tech/sendico/pkg/api/http"
	"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/pkg/mutil/mzap"
	"github.com/tech/sendico/server/interface/api/sresponse"
	emodel "github.com/tech/sendico/server/interface/model"
	"github.com/tech/sendico/server/internal/api/routers/ipguard"
	"go.uber.org/zap"
)

type tokenHandlerFunc = func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc

func (ar *AuthorizedRouter) validateClientPolicy(r *http.Request, t *emodel.AccountToken) http.HandlerFunc {
	clientID := strings.TrimSpace(t.ClientID)
	if clientID == "" {
		// Legacy tokens without client_id remain valid until expiration.
		return nil
	}
	client, err := ar.rtdb.GetClient(r.Context(), clientID)
	if errors.Is(err, merrors.ErrNoData) || client == nil {
		ar.logger.Debug("Client not found for access token", zap.String("client_id", clientID))
		return response.Unauthorized(ar.logger, ar.service, "client not found")
	}
	if err != nil {
		ar.logger.Warn("Failed to resolve client for access token", zap.Error(err), zap.String("client_id", clientID))
		return response.Internal(ar.logger, ar.service, err)
	}
	if client.IsRevoked {
		return response.Unauthorized(ar.logger, ar.service, "client has been revoked")
	}
	if client.AccountRef != nil && *client.AccountRef != t.AccountRef {
		return response.Unauthorized(ar.logger, ar.service, "client account mismatch")
	}

	clientIP := ipguard.ClientIP(r)
	allowed, err := ipguard.Allowed(clientIP, client.AllowedCIDRs)
	if err != nil {
		ar.logger.Warn("Client IP policy contains invalid CIDR", zap.Error(err), zap.String("client_id", clientID))
		return response.Forbidden(ar.logger, ar.service, "client_ip_policy_invalid", "client ip policy is invalid")
	}
	if !allowed {
		rawIP := ""
		if clientIP != nil {
			rawIP = clientIP.String()
		}
		ar.logger.Warn("Client IP policy denied authorized request", zap.String("client_id", clientID), zap.String("remote_ip", rawIP))
		return response.Forbidden(ar.logger, ar.service, "ip_not_allowed", "request ip is not allowed for this client")
	}

	return nil
}

func (ar *AuthorizedRouter) tokenHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler tokenHandlerFunc) {
	hndlr := func(r *http.Request) http.HandlerFunc {
		_, claims, err := jwtauth.FromContext(r.Context())
		if err != nil {
			ar.logger.Debug("Authorization failed", zap.Error(err), zap.String("request", r.URL.Path))
			return response.Unauthorized(ar.logger, ar.service, "credentials required")
		}
		t, err := emodel.Claims2Token(claims)
		if err != nil {
			ar.logger.Debug("Failed to decode account token", zap.Error(err))
			return response.BadRequest(ar.logger, ar.service, "credentials_unreadable", "faild to parse credentials")
		}
		if h := ar.validateClientPolicy(r, t); h != nil {
			return h
		}
		return handler(r, t)
	}
	ar.imp.InstallHandler(service, endpoint, method, hndlr)
}

func (ar *AuthorizedRouter) AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc) {
	hndlr := func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc {
		if t.Pending {
			return response.Unauthorized(ar.logger, ar.service, "additional verification required")
		}
		var a model.Account
		if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
			if errors.Is(err, merrors.ErrNoData) {
				ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.AccRef(t.AccountRef))
				return response.NotFound(ar.logger, ar.service, err.Error())
			}
			return response.Internal(ar.logger, ar.service, err)
		}
		accessToken, err := ar.imp.CreateAccessTokenForClient(&a, t.ClientID)
		if err != nil {
			ar.logger.Warn("Failed to generate access token", zap.Error(err))
			return response.Internal(ar.logger, ar.service, err)
		}
		return handler(r, &a, &accessToken)
	}
	ar.tokenHandler(service, endpoint, method, hndlr)
}

func (ar *AuthorizedRouter) PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc) {
	hndlr := func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc {
		var a model.Account
		if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil {
			if errors.Is(err, merrors.ErrNoData) {
				ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.AccRef(t.AccountRef))
				return response.NotFound(ar.logger, ar.service, err.Error())
			}
			return response.Internal(ar.logger, ar.service, err)
		}
		return handler(r, &a, t)
	}
	ar.tokenHandler(service, endpoint, method, hndlr)
}
