package usecase

import (
	"context"
	"errors"
	"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) ([]domain.Company, 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) ([]domain.Product, 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, error)

	CreateOrder(ctx context.Context, order *domain.Order) error
	ListOrders(ctx context.Context) ([]domain.Order, 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)
	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)
}

// 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
}

// 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) ([]domain.Company, error) {
	return s.repo.ListCompanies(ctx)
}

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

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) ([]domain.Product, error) {
	return s.repo.ListProducts(ctx)
}

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) ([]domain.InventoryItem, error) {
	return s.repo.ListInventory(ctx, filter)
}

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) ([]domain.Order, error) {
	return s.repo.ListOrders(ctx)
}

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, email, password string) (string, time.Time, error) {
	user, err := s.repo.GetUserByEmail(ctx, email)
	if err != nil {
		return "", time.Time{}, err
	}

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

	expiresAt := time.Now().Add(s.tokenTTL)
	claims := jwt.MapClaims{
		"sub":        user.ID.String(),
		"role":       user.Role,
		"company_id": user.CompanyID.String(),
		"exp":        expiresAt.Unix(),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	signed, err := token.SignedString(s.jwtSecret)
	if err != nil {
		return "", time.Time{}, err
	}

	return signed, expiresAt, nil
}

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

// 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
}
