package usecase

import (
	"context"
	"errors"
	"math"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"golang.org/x/crypto/bcrypt"

	"github.com/gofrs/uuid/v5"

	"github.com/saveinmed/backend-go/internal/domain"
)

// Repository defines DB contract for the core entities.
type Repository interface {
	CreateCompany(ctx context.Context, company *domain.Company) error
	ListCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.Company, int64, error)
	GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error)
	UpdateCompany(ctx context.Context, company *domain.Company) error
	DeleteCompany(ctx context.Context, id uuid.UUID) error

	CreateProduct(ctx context.Context, product *domain.Product) error
	ListProducts(ctx context.Context, filter domain.ProductFilter) ([]domain.Product, int64, error)
	SearchProducts(ctx context.Context, filter domain.ProductSearchFilter) ([]domain.ProductWithDistance, int64, error)
	ListRecords(ctx context.Context, filter domain.RecordSearchFilter) ([]domain.Product, int64, error)
	GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error)
	UpdateProduct(ctx context.Context, product *domain.Product) error
	DeleteProduct(ctx context.Context, id uuid.UUID) error
	AdjustInventory(ctx context.Context, productID uuid.UUID, delta int64, reason string) (*domain.InventoryItem, error)
	ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, int64, error)

	CreateOrder(ctx context.Context, order *domain.Order) error
	ListOrders(ctx context.Context, filter domain.OrderFilter) ([]domain.Order, int64, error)

	GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error)
	UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error
	DeleteOrder(ctx context.Context, id uuid.UUID) error
	CreateShipment(ctx context.Context, shipment *domain.Shipment) error
	GetShipmentByOrderID(ctx context.Context, orderID uuid.UUID) (*domain.Shipment, error)

	CreateUser(ctx context.Context, user *domain.User) error
	ListUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error)
	GetUser(ctx context.Context, id uuid.UUID) (*domain.User, error)
	GetUserByUsername(ctx context.Context, username string) (*domain.User, error)
	GetUserByEmail(ctx context.Context, email string) (*domain.User, error)
	UpdateUser(ctx context.Context, user *domain.User) error
	DeleteUser(ctx context.Context, id uuid.UUID) error

	AddCartItem(ctx context.Context, item *domain.CartItem) (*domain.CartItem, error)
	ListCartItems(ctx context.Context, buyerID uuid.UUID) ([]domain.CartItem, error)
	DeleteCartItem(ctx context.Context, id uuid.UUID, buyerID uuid.UUID) error

	CreateReview(ctx context.Context, review *domain.Review) error
	GetCompanyRating(ctx context.Context, companyID uuid.UUID) (*domain.CompanyRating, error)
	SellerDashboard(ctx context.Context, sellerID uuid.UUID) (*domain.SellerDashboard, error)
	AdminDashboard(ctx context.Context, since time.Time) (*domain.AdminDashboard, error)
	GetShippingSettings(ctx context.Context, vendorID uuid.UUID) (*domain.ShippingSettings, error)
	UpsertShippingSettings(ctx context.Context, settings *domain.ShippingSettings) error
	ListReviews(ctx context.Context, filter domain.ReviewFilter) ([]domain.Review, int64, error)
	ListShipments(ctx context.Context, filter domain.ShipmentFilter) ([]domain.Shipment, int64, error)
}

// PaymentGateway abstracts Mercado Pago integration.
type PaymentGateway interface {
	CreatePreference(ctx context.Context, order *domain.Order) (*domain.PaymentPreference, error)
}

type Service struct {
	repo                  Repository
	pay                   PaymentGateway
	jwtSecret             []byte
	tokenTTL              time.Duration
	marketplaceCommission float64
	passwordPepper        string
}

const (
	passwordResetTTL = 30 * time.Minute
)

// NewService wires use cases together.
func NewService(repo Repository, pay PaymentGateway, commissionPct float64, jwtSecret string, tokenTTL time.Duration, passwordPepper string) *Service {
	return &Service{
		repo:                  repo,
		pay:                   pay,
		jwtSecret:             []byte(jwtSecret),
		tokenTTL:              tokenTTL,
		marketplaceCommission: commissionPct,
		passwordPepper:        passwordPepper,
	}
}

func (s *Service) RegisterCompany(ctx context.Context, company *domain.Company) error {
	company.ID = uuid.Must(uuid.NewV7())
	return s.repo.CreateCompany(ctx, company)
}

func (s *Service) ListCompanies(ctx context.Context, filter domain.CompanyFilter, page, pageSize int) (*domain.CompanyPage, error) {
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}
	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize
	companies, total, err := s.repo.ListCompanies(ctx, filter)
	if err != nil {
		return nil, err
	}
	return &domain.CompanyPage{Companies: companies, Total: total, Page: page, PageSize: pageSize}, nil
}

func (s *Service) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) {
	return s.repo.GetCompany(ctx, id)
}

func (s *Service) CalculateShippingOptions(ctx context.Context, vendorID uuid.UUID, buyerLat, buyerLng float64, cartTotalCents int64) ([]domain.ShippingOption, error) {
	company, err := s.repo.GetCompany(ctx, vendorID)
	if err != nil {
		return nil, err
	}
	if company == nil {
		return nil, errors.New("vendor not found")
	}

	settings, err := s.repo.GetShippingSettings(ctx, vendorID)
	if err != nil {
		// Just return empty options if settings not found
		return []domain.ShippingOption{}, nil
	}
	if settings == nil {
		return []domain.ShippingOption{}, nil
	}

	distance := domain.HaversineDistance(company.Latitude, company.Longitude, buyerLat, buyerLng)
	var options []domain.ShippingOption

	// 1. Delivery
	if settings.Active {
		if settings.MaxRadiusKm > 0 && distance <= settings.MaxRadiusKm {
			variableCost := int64(math.Round(distance * float64(settings.PricePerKmCents)))
			price := settings.MinFeeCents
			if variableCost > price {
				price = variableCost
			}
			if settings.FreeShippingThresholdCents != nil && *settings.FreeShippingThresholdCents > 0 && cartTotalCents >= *settings.FreeShippingThresholdCents {
				price = 0
			}

			// Estimate: 30 mins base + 5 mins/km default
			estMins := 30 + int(math.Round(distance*5))

			options = append(options, domain.ShippingOption{
				Type:             domain.ShippingOptionTypeDelivery,
				ValueCents:       price,
				EstimatedMinutes: estMins,
				Description:      "Entrega Própria",
				DistanceKm:       distance,
			})
		}
	}

	// 2. Pickup
	if settings.PickupActive {
		desc := "Retirada na loja"
		if settings.PickupAddress != "" {
			desc = "Retirada em: " + settings.PickupAddress
		}
		if settings.PickupHours != "" {
			desc += " (" + settings.PickupHours + ")"
		}

		options = append(options, domain.ShippingOption{
			Type:             domain.ShippingOptionTypePickup,
			ValueCents:       0,
			EstimatedMinutes: 60, // Default 1 hour readily available
			Description:      desc,
			DistanceKm:       distance,
		})
	}

	return options, nil
}

func (s *Service) UpdateCompany(ctx context.Context, company *domain.Company) error {
	return s.repo.UpdateCompany(ctx, company)
}

func (s *Service) DeleteCompany(ctx context.Context, id uuid.UUID) error {
	return s.repo.DeleteCompany(ctx, id)
}

func (s *Service) RegisterProduct(ctx context.Context, product *domain.Product) error {
	product.ID = uuid.Must(uuid.NewV7())
	return s.repo.CreateProduct(ctx, product)
}

func (s *Service) ListProducts(ctx context.Context, filter domain.ProductFilter, page, pageSize int) (*domain.ProductPage, error) {
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}
	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize
	products, total, err := s.repo.ListProducts(ctx, filter)
	if err != nil {
		return nil, err
	}
	return &domain.ProductPage{Products: products, Total: total, Page: page, PageSize: pageSize}, nil
}

// SearchProducts returns products with distance, ordered by expiration date.
// Seller info is anonymized until checkout.
func (s *Service) SearchProducts(ctx context.Context, filter domain.ProductSearchFilter, page, pageSize int) (*domain.ProductSearchPage, error) {
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}
	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize
	products, total, err := s.repo.SearchProducts(ctx, filter)
	if err != nil {
		return nil, err
	}
	return &domain.ProductSearchPage{Products: products, Total: total, Page: page, PageSize: pageSize}, nil
}

// ListRecords provides an advanced search for marketplace listings.
func (s *Service) ListRecords(ctx context.Context, req domain.SearchRequest) (*domain.PaginationResponse[domain.Product], error) {
	page := req.Page
	pageSize := req.PageSize
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}

	sortBy := strings.TrimSpace(req.SortBy)
	if sortBy == "" {
		sortBy = "updated_at"
	}
	sortOrder := strings.ToLower(strings.TrimSpace(req.SortOrder))
	if sortOrder == "" {
		sortOrder = "desc"
	}

	filter := domain.RecordSearchFilter{
		Query:         strings.TrimSpace(req.Query),
		SortBy:        sortBy,
		SortOrder:     sortOrder,
		CreatedAfter:  req.CreatedAfter,
		CreatedBefore: req.CreatedBefore,
		Limit:         pageSize,
		Offset:        (page - 1) * pageSize,
	}

	items, total, err := s.repo.ListRecords(ctx, filter)
	if err != nil {
		return nil, err
	}

	totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
	return &domain.PaginationResponse[domain.Product]{
		Items:       items,
		TotalCount:  total,
		CurrentPage: page,
		TotalPages:  totalPages,
	}, nil
}

func (s *Service) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) {
	return s.repo.GetProduct(ctx, id)
}

func (s *Service) UpdateProduct(ctx context.Context, product *domain.Product) error {
	return s.repo.UpdateProduct(ctx, product)
}

func (s *Service) DeleteProduct(ctx context.Context, id uuid.UUID) error {
	return s.repo.DeleteProduct(ctx, id)
}

func (s *Service) ListInventory(ctx context.Context, filter domain.InventoryFilter, page, pageSize int) (*domain.InventoryPage, error) {
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}
	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize
	items, total, err := s.repo.ListInventory(ctx, filter)
	if err != nil {
		return nil, err
	}
	return &domain.InventoryPage{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
}

func (s *Service) AdjustInventory(ctx context.Context, productID uuid.UUID, delta int64, reason string) (*domain.InventoryItem, error) {
	return s.repo.AdjustInventory(ctx, productID, delta, reason)
}

func (s *Service) CreateOrder(ctx context.Context, order *domain.Order) error {
	order.ID = uuid.Must(uuid.NewV7())
	order.Status = domain.OrderStatusPending
	return s.repo.CreateOrder(ctx, order)
}

func (s *Service) ListOrders(ctx context.Context, filter domain.OrderFilter, page, pageSize int) (*domain.OrderPage, error) {
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}
	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize
	orders, total, err := s.repo.ListOrders(ctx, filter)
	if err != nil {
		return nil, err
	}
	return &domain.OrderPage{Orders: orders, Total: total, Page: page, PageSize: pageSize}, nil
}

func (s *Service) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
	return s.repo.GetOrder(ctx, id)
}

func (s *Service) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error {
	return s.repo.UpdateOrderStatus(ctx, id, status)
}

func (s *Service) DeleteOrder(ctx context.Context, id uuid.UUID) error {
	return s.repo.DeleteOrder(ctx, id)
}

// CreateShipment persists a freight label for an order if not already present.
func (s *Service) CreateShipment(ctx context.Context, shipment *domain.Shipment) error {
	if _, err := s.repo.GetOrder(ctx, shipment.OrderID); err != nil {
		return err
	}

	shipment.ID = uuid.Must(uuid.NewV7())
	shipment.Status = "Label gerada"
	return s.repo.CreateShipment(ctx, shipment)
}

// GetShipmentByOrderID returns freight details for an order.
func (s *Service) GetShipmentByOrderID(ctx context.Context, orderID uuid.UUID) (*domain.Shipment, error) {
	return s.repo.GetShipmentByOrderID(ctx, orderID)
}

func (s *Service) CreatePaymentPreference(ctx context.Context, id uuid.UUID) (*domain.PaymentPreference, error) {
	order, err := s.repo.GetOrder(ctx, id)
	if err != nil {
		return nil, err
	}
	return s.pay.CreatePreference(ctx, order)
}

// HandlePaymentWebhook processes Mercado Pago notifications ensuring split consistency.
func (s *Service) HandlePaymentWebhook(ctx context.Context, event domain.PaymentWebhookEvent) (*domain.PaymentSplitResult, error) {
	order, err := s.repo.GetOrder(ctx, event.OrderID)
	if err != nil {
		return nil, err
	}

	expectedMarketplaceFee := int64(float64(order.TotalCents) * (s.marketplaceCommission / 100))
	marketplaceFee := event.MarketplaceFee
	if marketplaceFee == 0 {
		marketplaceFee = expectedMarketplaceFee
	}

	sellerReceivable := order.TotalCents - marketplaceFee
	if event.SellerAmount > 0 {
		sellerReceivable = event.SellerAmount
	}

	if strings.EqualFold(event.Status, "approved") || strings.EqualFold(event.Status, "paid") {
		if err := s.repo.UpdateOrderStatus(ctx, order.ID, domain.OrderStatusPaid); err != nil {
			return nil, err
		}
	}

	return &domain.PaymentSplitResult{
		OrderID:          order.ID,
		PaymentID:        event.PaymentID,
		Status:           event.Status,
		MarketplaceFee:   marketplaceFee,
		SellerReceivable: sellerReceivable,
		TotalPaidAmount:  event.TotalPaidAmount,
	}, nil
}

func (s *Service) CreateUser(ctx context.Context, user *domain.User, password string) error {
	hashed, err := bcrypt.GenerateFromPassword([]byte(s.pepperPassword(password)), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	user.ID = uuid.Must(uuid.NewV7())
	user.PasswordHash = string(hashed)

	return s.repo.CreateUser(ctx, user)
}

func (s *Service) ListUsers(ctx context.Context, filter domain.UserFilter, page, pageSize int) (*domain.UserPage, error) {
	if page < 1 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = 20
	}

	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize

	users, total, err := s.repo.ListUsers(ctx, filter)
	if err != nil {
		return nil, err
	}

	return &domain.UserPage{Users: users, Total: total, Page: page, PageSize: pageSize}, nil
}

func (s *Service) GetUser(ctx context.Context, id uuid.UUID) (*domain.User, error) {
	return s.repo.GetUser(ctx, id)
}

func (s *Service) UpdateUser(ctx context.Context, user *domain.User, newPassword string) error {
	if newPassword != "" {
		hashed, err := bcrypt.GenerateFromPassword([]byte(s.pepperPassword(newPassword)), bcrypt.DefaultCost)
		if err != nil {
			return err
		}
		user.PasswordHash = string(hashed)
	}

	return s.repo.UpdateUser(ctx, user)
}

func (s *Service) DeleteUser(ctx context.Context, id uuid.UUID) error {
	return s.repo.DeleteUser(ctx, id)
}

// AddItemToCart validates stock, persists the item and returns the refreshed summary.
func (s *Service) AddItemToCart(ctx context.Context, buyerID, productID uuid.UUID, quantity int64) (*domain.CartSummary, error) {
	if quantity <= 0 {
		return nil, errors.New("quantity must be greater than zero")
	}

	product, err := s.repo.GetProduct(ctx, productID)
	if err != nil {
		return nil, err
	}

	cartItems, err := s.repo.ListCartItems(ctx, buyerID)
	if err != nil {
		return nil, err
	}

	var currentQty int64
	for _, it := range cartItems {
		if it.ProductID == productID {
			currentQty += it.Quantity
		}
	}

	if product.Stock < currentQty+quantity {
		return nil, errors.New("insufficient stock for requested quantity")
	}

	_, err = s.repo.AddCartItem(ctx, &domain.CartItem{
		ID:        uuid.Must(uuid.NewV7()),
		BuyerID:   buyerID,
		ProductID: productID,
		Quantity:  quantity,
		UnitCents: product.PriceCents,
		Batch:     product.Batch,
		ExpiresAt: product.ExpiresAt,
	})
	if err != nil {
		return nil, err
	}

	return s.cartSummary(ctx, buyerID)
}

// ListCart returns the current cart with B2B discounts applied.
func (s *Service) ListCart(ctx context.Context, buyerID uuid.UUID) (*domain.CartSummary, error) {
	return s.cartSummary(ctx, buyerID)
}

// RemoveCartItem deletes a cart row and returns the refreshed cart summary.
func (s *Service) RemoveCartItem(ctx context.Context, buyerID, cartItemID uuid.UUID) (*domain.CartSummary, error) {
	if err := s.repo.DeleteCartItem(ctx, cartItemID, buyerID); err != nil {
		return nil, err
	}
	return s.cartSummary(ctx, buyerID)
}

func (s *Service) cartSummary(ctx context.Context, buyerID uuid.UUID) (*domain.CartSummary, error) {
	items, err := s.repo.ListCartItems(ctx, buyerID)
	if err != nil {
		return nil, err
	}

	var subtotal int64
	for i := range items {
		subtotal += items[i].UnitCents * items[i].Quantity
	}

	summary := &domain.CartSummary{
		Items:         items,
		SubtotalCents: subtotal,
	}

	if subtotal >= 100000 { // apply 5% B2B discount for large baskets
		summary.DiscountCents = int64(float64(subtotal) * 0.05)
		summary.DiscountReason = "5% B2B volume discount"
	}
	summary.TotalCents = subtotal - summary.DiscountCents
	return summary, nil
}

// CreateReview stores a buyer rating ensuring the order is delivered and owned by the requester.
func (s *Service) CreateReview(ctx context.Context, buyerID, orderID uuid.UUID, rating int, comment string) (*domain.Review, error) {
	if rating < 1 || rating > 5 {
		return nil, errors.New("rating must be between 1 and 5")
	}

	order, err := s.repo.GetOrder(ctx, orderID)
	if err != nil {
		return nil, err
	}

	if order.Status != domain.OrderStatusDelivered {
		return nil, errors.New("only delivered orders can be reviewed")
	}

	if order.BuyerID != buyerID {
		return nil, errors.New("order does not belong to buyer")
	}

	review := &domain.Review{
		ID:       uuid.Must(uuid.NewV7()),
		OrderID:  orderID,
		BuyerID:  buyerID,
		SellerID: order.SellerID,
		Rating:   rating,
		Comment:  comment,
	}

	if err := s.repo.CreateReview(ctx, review); err != nil {
		return nil, err
	}
	return review, nil
}

// GetCompanyRating returns the average rating for a seller or pharmacy.
func (s *Service) GetCompanyRating(ctx context.Context, companyID uuid.UUID) (*domain.CompanyRating, error) {
	return s.repo.GetCompanyRating(ctx, companyID)
}

// GetSellerDashboard aggregates commercial KPIs for the seller.
func (s *Service) GetSellerDashboard(ctx context.Context, sellerID uuid.UUID) (*domain.SellerDashboard, error) {
	return s.repo.SellerDashboard(ctx, sellerID)
}

// GetAdminDashboard exposes marketplace-wide metrics for the last 30 days.
func (s *Service) GetAdminDashboard(ctx context.Context) (*domain.AdminDashboard, error) {
	since := time.Now().AddDate(0, 0, -30)
	return s.repo.AdminDashboard(ctx, since)
}

// RegisterAccount creates a company when needed and persists a user bound to it.
func (s *Service) RegisterAccount(ctx context.Context, company *domain.Company, user *domain.User, password string) error {
	if company != nil {
		if company.ID == uuid.Nil {
			company.ID = uuid.Must(uuid.NewV7())
			company.IsVerified = false
			if err := s.repo.CreateCompany(ctx, company); err != nil {
				return err
			}
		} else {
			if _, err := s.repo.GetCompany(ctx, company.ID); err != nil {
				return err
			}
		}
		user.CompanyID = company.ID
	}

	return s.CreateUser(ctx, user, password)
}

// Authenticate validates credentials and emits a signed JWT.
func (s *Service) Authenticate(ctx context.Context, username, password string) (string, time.Time, error) {
	user, err := s.repo.GetUserByUsername(ctx, username)
	if err != nil {
		// Return generic error to avoid leaking DB details or user existence
		return "", time.Time{}, errors.New("invalid credentials")
	}

	if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(s.pepperPassword(password))); err != nil {
		return "", time.Time{}, errors.New("invalid credentials")
	}

	return s.issueAccessToken(user)
}

func (s *Service) pepperPassword(password string) string {
	if s.passwordPepper == "" {
		return password
	}
	return password + s.passwordPepper
}

// RefreshToken validates the provided JWT and issues a new access token.
func (s *Service) RefreshToken(ctx context.Context, tokenStr string) (string, time.Time, error) {
	claims, err := s.parseToken(tokenStr)
	if err != nil {
		return "", time.Time{}, err
	}

	if scope, ok := claims["scope"].(string); ok && scope != "" {
		return "", time.Time{}, errors.New("invalid token scope")
	}

	sub, ok := claims["sub"].(string)
	if !ok || sub == "" {
		return "", time.Time{}, errors.New("invalid token subject")
	}

	userID, err := uuid.FromString(sub)
	if err != nil {
		return "", time.Time{}, errors.New("invalid token subject")
	}

	user, err := s.repo.GetUser(ctx, userID)
	if err != nil {
		return "", time.Time{}, err
	}

	return s.issueAccessToken(user)
}

// CreatePasswordResetToken generates a short-lived token for password reset.
func (s *Service) CreatePasswordResetToken(ctx context.Context, email string) (string, time.Time, error) {
	user, err := s.repo.GetUserByEmail(ctx, email)
	if err != nil {
		return "", time.Time{}, err
	}

	expiresAt := time.Now().Add(passwordResetTTL)
	claims := jwt.MapClaims{
		"sub":   user.ID.String(),
		"scope": "password_reset",
	}
	signed, err := s.signToken(claims, expiresAt)
	if err != nil {
		return "", time.Time{}, err
	}
	return signed, expiresAt, nil
}

// ResetPassword validates the reset token and updates the user password.
func (s *Service) ResetPassword(ctx context.Context, tokenStr, newPassword string) error {
	claims, err := s.parseToken(tokenStr)
	if err != nil {
		return err
	}

	scope, _ := claims["scope"].(string)
	if scope != "password_reset" {
		return errors.New("invalid token scope")
	}

	sub, ok := claims["sub"].(string)
	if !ok || sub == "" {
		return errors.New("invalid token subject")
	}

	userID, err := uuid.FromString(sub)
	if err != nil {
		return errors.New("invalid token subject")
	}

	user, err := s.repo.GetUser(ctx, userID)
	if err != nil {
		return err
	}

	return s.UpdateUser(ctx, user, newPassword)
}

// VerifyEmail marks the user email as verified based on a JWT token.
func (s *Service) VerifyEmail(ctx context.Context, tokenStr string) (*domain.User, error) {
	claims, err := s.parseToken(tokenStr)
	if err != nil {
		return nil, err
	}

	if scope, ok := claims["scope"].(string); ok && scope != "" && scope != "email_verify" {
		return nil, errors.New("invalid token scope")
	}

	sub, ok := claims["sub"].(string)
	if !ok || sub == "" {
		return nil, errors.New("invalid token subject")
	}

	userID, err := uuid.FromString(sub)
	if err != nil {
		return nil, errors.New("invalid token subject")
	}

	user, err := s.repo.GetUser(ctx, userID)
	if err != nil {
		return nil, err
	}

	if !user.EmailVerified {
		user.EmailVerified = true
		if err := s.repo.UpdateUser(ctx, user); err != nil {
			return nil, err
		}
	}

	return user, nil
}

func (s *Service) issueAccessToken(user *domain.User) (string, time.Time, error) {
	expiresAt := time.Now().Add(s.tokenTTL)
	claims := jwt.MapClaims{
		"sub":        user.ID.String(),
		"role":       user.Role,
		"company_id": user.CompanyID.String(),
	}
	signed, err := s.signToken(claims, expiresAt)
	if err != nil {
		return "", time.Time{}, err
	}
	return signed, expiresAt, nil
}

func (s *Service) signToken(claims jwt.MapClaims, expiresAt time.Time) (string, error) {
	claims["exp"] = expiresAt.Unix()
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(s.jwtSecret)
}

func (s *Service) parseToken(tokenStr string) (jwt.MapClaims, error) {
	if strings.TrimSpace(tokenStr) == "" {
		return nil, errors.New("token is required")
	}

	claims := jwt.MapClaims{}
	parser := jwt.NewParser(jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
	token, err := parser.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (any, error) {
		return s.jwtSecret, nil
	})
	if err != nil || !token.Valid {
		return nil, errors.New("invalid token")
	}
	return claims, nil
}

// VerifyCompany marks a company as verified.
func (s *Service) VerifyCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) {
	company, err := s.repo.GetCompany(ctx, id)
	if err != nil {
		return nil, err
	}
	company.IsVerified = true

	if err := s.repo.UpdateCompany(ctx, company); err != nil {
		return nil, err
	}
	return company, nil
}

func (s *Service) ListReviews(ctx context.Context, filter domain.ReviewFilter, page, pageSize int) (*domain.ReviewPage, error) {
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}
	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize
	reviews, total, err := s.repo.ListReviews(ctx, filter)
	if err != nil {
		return nil, err
	}
	return &domain.ReviewPage{Reviews: reviews, Total: total, Page: page, PageSize: pageSize}, nil
}

func (s *Service) ListShipments(ctx context.Context, filter domain.ShipmentFilter, page, pageSize int) (*domain.ShipmentPage, error) {
	if pageSize <= 0 {
		pageSize = 20
	}
	if page <= 0 {
		page = 1
	}
	filter.Limit = pageSize
	filter.Offset = (page - 1) * pageSize
	shipments, total, err := s.repo.ListShipments(ctx, filter)
	if err != nil {
		return nil, err
	}
	return &domain.ShipmentPage{Shipments: shipments, Total: total, Page: page, PageSize: pageSize}, nil
}

func (s *Service) GetShippingSettings(ctx context.Context, vendorID uuid.UUID) (*domain.ShippingSettings, error) {
	return s.repo.GetShippingSettings(ctx, vendorID)
}

func (s *Service) UpsertShippingSettings(ctx context.Context, settings *domain.ShippingSettings) error {
	return s.repo.UpsertShippingSettings(ctx, settings)
}
