package postgres

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/gofrs/uuid/v5"
	"github.com/jmoiron/sqlx"

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

// Repository implements the data access layer using sqlx + pgx.
type Repository struct {
	db *sqlx.DB
}

// New creates a Postgres-backed repository and configures pooling.
func New(db *sqlx.DB) *Repository {
	return &Repository{db: db}
}

func (r *Repository) CreateCompany(ctx context.Context, company *domain.Company) error {
	now := time.Now().UTC()
	company.CreatedAt = now
	company.UpdatedAt = now

	query := `INSERT INTO companies (id, role, cnpj, corporate_name, license_number, is_verified, created_at, updated_at)
VALUES (:id, :role, :cnpj, :corporate_name, :license_number, :is_verified, :created_at, :updated_at)`

	_, err := r.db.NamedExecContext(ctx, query, company)
	return err
}

func (r *Repository) ListCompanies(ctx context.Context) ([]domain.Company, error) {
	var companies []domain.Company
	query := `SELECT id, role, cnpj, corporate_name, license_number, is_verified, created_at, updated_at FROM companies ORDER BY created_at DESC`
	if err := r.db.SelectContext(ctx, &companies, query); err != nil {
		return nil, err
	}
	return companies, nil
}

func (r *Repository) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) {
	var company domain.Company
	query := `SELECT id, role, cnpj, corporate_name, license_number, is_verified, created_at, updated_at FROM companies WHERE id = $1`
	if err := r.db.GetContext(ctx, &company, query, id); err != nil {
		return nil, err
	}
	return &company, nil
}

func (r *Repository) UpdateCompany(ctx context.Context, company *domain.Company) error {
	company.UpdatedAt = time.Now().UTC()

	query := `UPDATE companies
SET role = :role, cnpj = :cnpj, corporate_name = :corporate_name, license_number = :license_number, is_verified = :is_verified, updated_at = :updated_at
WHERE id = :id`

	res, err := r.db.NamedExecContext(ctx, query, company)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows == 0 {
		return errors.New("company not found")
	}
	return nil
}

func (r *Repository) DeleteCompany(ctx context.Context, id uuid.UUID) error {
	var count int
	if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM users WHERE company_id = $1`, id); err != nil {
		return err
	}
	if count > 0 {
		return errors.New("company has related users")
	}
	if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM products WHERE seller_id = $1`, id); err != nil {
		return err
	}
	if count > 0 {
		return errors.New("company has related products")
	}
	if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM orders WHERE buyer_id = $1 OR seller_id = $1`, id); err != nil {
		return err
	}
	if count > 0 {
		return errors.New("company has related orders")
	}
	if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM reviews WHERE buyer_id = $1 OR seller_id = $1`, id); err != nil {
		return err
	}
	if count > 0 {
		return errors.New("company has related reviews")
	}
	if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM cart_items WHERE buyer_id = $1`, id); err != nil {
		return err
	}
	if count > 0 {
		return errors.New("company has related cart items")
	}

	res, err := r.db.ExecContext(ctx, `DELETE FROM companies WHERE id = $1`, id)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows == 0 {
		return errors.New("company not found")
	}
	return nil
}

func (r *Repository) CreateProduct(ctx context.Context, product *domain.Product) error {
	now := time.Now().UTC()
	product.CreatedAt = now
	product.UpdatedAt = now

	query := `INSERT INTO products (id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at)
VALUES (:id, :seller_id, :name, :description, :batch, :expires_at, :price_cents, :stock, :created_at, :updated_at)`

	_, err := r.db.NamedExecContext(ctx, query, product)
	return err
}

func (r *Repository) ListProducts(ctx context.Context) ([]domain.Product, error) {
	var products []domain.Product
	query := `SELECT id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at FROM products ORDER BY created_at DESC`
	if err := r.db.SelectContext(ctx, &products, query); err != nil {
		return nil, err
	}
	return products, nil
}

func (r *Repository) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) {
	var product domain.Product
	query := `SELECT id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at FROM products WHERE id = $1`
	if err := r.db.GetContext(ctx, &product, query, id); err != nil {
		return nil, err
	}
	return &product, nil
}

func (r *Repository) UpdateProduct(ctx context.Context, product *domain.Product) error {
	product.UpdatedAt = time.Now().UTC()

	query := `UPDATE products
SET seller_id = :seller_id, name = :name, description = :description, batch = :batch, expires_at = :expires_at, price_cents = :price_cents, stock = :stock, updated_at = :updated_at
WHERE id = :id`

	res, err := r.db.NamedExecContext(ctx, query, product)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows == 0 {
		return errors.New("product not found")
	}
	return nil
}

func (r *Repository) DeleteProduct(ctx context.Context, id uuid.UUID) error {
	var count int
	if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM order_items WHERE product_id = $1`, id); err != nil {
		return err
	}
	if count > 0 {
		return errors.New("product has related orders")
	}

	tx, err := r.db.BeginTxx(ctx, nil)
	if err != nil {
		return err
	}

	if _, err := tx.ExecContext(ctx, `DELETE FROM inventory_adjustments WHERE product_id = $1`, id); err != nil {
		_ = tx.Rollback()
		return err
	}
	if _, err := tx.ExecContext(ctx, `DELETE FROM cart_items WHERE product_id = $1`, id); err != nil {
		_ = tx.Rollback()
		return err
	}

	res, err := tx.ExecContext(ctx, `DELETE FROM products WHERE id = $1`, id)
	if err != nil {
		_ = tx.Rollback()
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		_ = tx.Rollback()
		return err
	}
	if rows == 0 {
		_ = tx.Rollback()
		return errors.New("product not found")
	}

	return tx.Commit()
}

func (r *Repository) CreateOrder(ctx context.Context, order *domain.Order) error {
	now := time.Now().UTC()
	order.CreatedAt = now
	order.UpdatedAt = now

	tx, err := r.db.BeginTxx(ctx, nil)
	if err != nil {
		return err
	}

	orderQuery := `INSERT INTO orders (id, buyer_id, seller_id, status, total_cents, shipping_recipient_name, shipping_street, shipping_number, shipping_complement, shipping_district, shipping_city, shipping_state, shipping_zip_code, shipping_country, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)`
	if _, err := tx.ExecContext(ctx, orderQuery, order.ID, order.BuyerID, order.SellerID, order.Status, order.TotalCents, order.Shipping.RecipientName, order.Shipping.Street, order.Shipping.Number, order.Shipping.Complement, order.Shipping.District, order.Shipping.City, order.Shipping.State, order.Shipping.ZipCode, order.Shipping.Country, order.CreatedAt, order.UpdatedAt); err != nil {
		_ = tx.Rollback()
		return err
	}

	itemQuery := `INSERT INTO order_items (id, order_id, product_id, quantity, unit_cents, batch, expires_at) VALUES ($1, $2, $3, $4, $5, $6, $7)`
	for i := range order.Items {
		item := &order.Items[i]
		item.ID = uuid.Must(uuid.NewV7())
		item.OrderID = order.ID
		if _, err := tx.ExecContext(ctx, itemQuery, item.ID, item.OrderID, item.ProductID, item.Quantity, item.UnitCents, item.Batch, item.ExpiresAt); err != nil {
			_ = tx.Rollback()
			return err
		}
	}

	return tx.Commit()
}

func (r *Repository) ListOrders(ctx context.Context) ([]domain.Order, error) {
	var rows []struct {
		ID                    uuid.UUID          `db:"id"`
		BuyerID               uuid.UUID          `db:"buyer_id"`
		SellerID              uuid.UUID          `db:"seller_id"`
		Status                domain.OrderStatus `db:"status"`
		TotalCents            int64              `db:"total_cents"`
		ShippingRecipientName string             `db:"shipping_recipient_name"`
		ShippingStreet        string             `db:"shipping_street"`
		ShippingNumber        string             `db:"shipping_number"`
		ShippingComplement    string             `db:"shipping_complement"`
		ShippingDistrict      string             `db:"shipping_district"`
		ShippingCity          string             `db:"shipping_city"`
		ShippingState         string             `db:"shipping_state"`
		ShippingZipCode       string             `db:"shipping_zip_code"`
		ShippingCountry       string             `db:"shipping_country"`
		CreatedAt             time.Time          `db:"created_at"`
		UpdatedAt             time.Time          `db:"updated_at"`
	}
	query := `SELECT id, buyer_id, seller_id, status, total_cents, shipping_recipient_name, shipping_street, shipping_number, shipping_complement, shipping_district, shipping_city, shipping_state, shipping_zip_code, shipping_country, created_at, updated_at FROM orders ORDER BY created_at DESC`
	if err := r.db.SelectContext(ctx, &rows, query); err != nil {
		return nil, err
	}

	orders := make([]domain.Order, 0, len(rows))
	itemQuery := `SELECT id, order_id, product_id, quantity, unit_cents, batch, expires_at FROM order_items WHERE order_id = $1`
	for _, row := range rows {
		var items []domain.OrderItem
		if err := r.db.SelectContext(ctx, &items, itemQuery, row.ID); err != nil {
			return nil, err
		}
		orders = append(orders, domain.Order{
			ID:         row.ID,
			BuyerID:    row.BuyerID,
			SellerID:   row.SellerID,
			Status:     row.Status,
			TotalCents: row.TotalCents,
			Items:      items,
			Shipping: domain.ShippingAddress{
				RecipientName: row.ShippingRecipientName,
				Street:        row.ShippingStreet,
				Number:        row.ShippingNumber,
				Complement:    row.ShippingComplement,
				District:      row.ShippingDistrict,
				City:          row.ShippingCity,
				State:         row.ShippingState,
				ZipCode:       row.ShippingZipCode,
				Country:       row.ShippingCountry,
			},
			CreatedAt: row.CreatedAt,
			UpdatedAt: row.UpdatedAt,
		})
	}
	return orders, nil
}

func (r *Repository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
	var row struct {
		ID                    uuid.UUID          `db:"id"`
		BuyerID               uuid.UUID          `db:"buyer_id"`
		SellerID              uuid.UUID          `db:"seller_id"`
		Status                domain.OrderStatus `db:"status"`
		TotalCents            int64              `db:"total_cents"`
		ShippingRecipientName string             `db:"shipping_recipient_name"`
		ShippingStreet        string             `db:"shipping_street"`
		ShippingNumber        string             `db:"shipping_number"`
		ShippingComplement    string             `db:"shipping_complement"`
		ShippingDistrict      string             `db:"shipping_district"`
		ShippingCity          string             `db:"shipping_city"`
		ShippingState         string             `db:"shipping_state"`
		ShippingZipCode       string             `db:"shipping_zip_code"`
		ShippingCountry       string             `db:"shipping_country"`
		CreatedAt             time.Time          `db:"created_at"`
		UpdatedAt             time.Time          `db:"updated_at"`
	}
	orderQuery := `SELECT id, buyer_id, seller_id, status, total_cents, shipping_recipient_name, shipping_street, shipping_number, shipping_complement, shipping_district, shipping_city, shipping_state, shipping_zip_code, shipping_country, created_at, updated_at FROM orders WHERE id = $1`
	if err := r.db.GetContext(ctx, &row, orderQuery, id); err != nil {
		return nil, err
	}

	var items []domain.OrderItem
	itemQuery := `SELECT id, order_id, product_id, quantity, unit_cents, batch, expires_at FROM order_items WHERE order_id = $1`
	if err := r.db.SelectContext(ctx, &items, itemQuery, id); err != nil {
		return nil, err
	}
	order := &domain.Order{
		ID:         row.ID,
		BuyerID:    row.BuyerID,
		SellerID:   row.SellerID,
		Status:     row.Status,
		TotalCents: row.TotalCents,
		Items:      items,
		Shipping: domain.ShippingAddress{
			RecipientName: row.ShippingRecipientName,
			Street:        row.ShippingStreet,
			Number:        row.ShippingNumber,
			Complement:    row.ShippingComplement,
			District:      row.ShippingDistrict,
			City:          row.ShippingCity,
			State:         row.ShippingState,
			ZipCode:       row.ShippingZipCode,
			Country:       row.ShippingCountry,
		},
		CreatedAt: row.CreatedAt,
		UpdatedAt: row.UpdatedAt,
	}
	return order, nil
}

func (r *Repository) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error {
	query := `UPDATE orders SET status = $1, updated_at = $2 WHERE id = $3`
	res, err := r.db.ExecContext(ctx, query, status, time.Now().UTC(), id)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows == 0 {
		return errors.New("order not found")
	}
	return nil
}

func (r *Repository) DeleteOrder(ctx context.Context, id uuid.UUID) error {
	tx, err := r.db.BeginTxx(ctx, nil)
	if err != nil {
		return err
	}

	if _, err := tx.ExecContext(ctx, `DELETE FROM reviews WHERE order_id = $1`, id); err != nil {
		_ = tx.Rollback()
		return err
	}
	if _, err := tx.ExecContext(ctx, `DELETE FROM shipments WHERE order_id = $1`, id); err != nil {
		_ = tx.Rollback()
		return err
	}
	if _, err := tx.ExecContext(ctx, `DELETE FROM order_items WHERE order_id = $1`, id); err != nil {
		_ = tx.Rollback()
		return err
	}

	res, err := tx.ExecContext(ctx, `DELETE FROM orders WHERE id = $1`, id)
	if err != nil {
		_ = tx.Rollback()
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		_ = tx.Rollback()
		return err
	}
	if rows == 0 {
		_ = tx.Rollback()
		return errors.New("order not found")
	}

	return tx.Commit()
}

func (r *Repository) CreateShipment(ctx context.Context, shipment *domain.Shipment) error {
	now := time.Now().UTC()
	shipment.CreatedAt = now
	shipment.UpdatedAt = now

	query := `INSERT INTO shipments (id, order_id, carrier, tracking_code, external_tracking, status, created_at, updated_at)
VALUES (:id, :order_id, :carrier, :tracking_code, :external_tracking, :status, :created_at, :updated_at)`

	_, err := r.db.NamedExecContext(ctx, query, shipment)
	return err
}

func (r *Repository) GetShipmentByOrderID(ctx context.Context, orderID uuid.UUID) (*domain.Shipment, error) {
	var shipment domain.Shipment
	query := `SELECT id, order_id, carrier, tracking_code, external_tracking, status, created_at, updated_at FROM shipments WHERE order_id = $1`
	if err := r.db.GetContext(ctx, &shipment, query, orderID); err != nil {
		return nil, err
	}
	return &shipment, nil
}

func (r *Repository) AdjustInventory(ctx context.Context, productID uuid.UUID, delta int64, reason string) (*domain.InventoryItem, error) {
	tx, err := r.db.BeginTxx(ctx, nil)
	if err != nil {
		return nil, err
	}

	var product domain.Product
	if err := tx.GetContext(ctx, &product, `SELECT id, seller_id, name, batch, expires_at, price_cents, stock, updated_at FROM products WHERE id = $1 FOR UPDATE`, productID); err != nil {
		_ = tx.Rollback()
		return nil, err
	}

	newStock := product.Stock + delta
	if newStock < 0 {
		_ = tx.Rollback()
		return nil, errors.New("inventory cannot be negative")
	}

	now := time.Now().UTC()
	if _, err := tx.ExecContext(ctx, `UPDATE products SET stock = $1, updated_at = $2 WHERE id = $3`, newStock, now, productID); err != nil {
		_ = tx.Rollback()
		return nil, err
	}

	adj := domain.InventoryAdjustment{
		ID:        uuid.Must(uuid.NewV7()),
		ProductID: productID,
		Delta:     delta,
		Reason:    reason,
		CreatedAt: now,
	}

	if _, err := tx.NamedExecContext(ctx, `INSERT INTO inventory_adjustments (id, product_id, delta, reason, created_at) VALUES (:id, :product_id, :delta, :reason, :created_at)`, &adj); err != nil {
		_ = tx.Rollback()
		return nil, err
	}

	if err := tx.Commit(); err != nil {
		return nil, err
	}

	return &domain.InventoryItem{
		ProductID:  productID,
		SellerID:   product.SellerID,
		Name:       product.Name,
		Batch:      product.Batch,
		ExpiresAt:  product.ExpiresAt,
		Quantity:   newStock,
		PriceCents: product.PriceCents,
		UpdatedAt:  now,
	}, nil
}

func (r *Repository) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, error) {
	args := []any{}
	clauses := []string{}
	if filter.ExpiringBefore != nil {
		clauses = append(clauses, fmt.Sprintf("expires_at <= $%d", len(args)+1))
		args = append(args, *filter.ExpiringBefore)
	}

	where := ""
	if len(clauses) > 0 {
		where = " WHERE " + strings.Join(clauses, " AND ")
	}

	query := fmt.Sprintf(`SELECT id AS product_id, seller_id, name, batch, expires_at, stock AS quantity, price_cents, updated_at FROM products%s ORDER BY expires_at ASC`, where)
	var items []domain.InventoryItem
	if err := r.db.SelectContext(ctx, &items, query, args...); err != nil {
		return nil, err
	}
	return items, nil
}

func (r *Repository) CreateUser(ctx context.Context, user *domain.User) error {
	now := time.Now().UTC()
	user.CreatedAt = now
	user.UpdatedAt = now

	query := `INSERT INTO users (id, company_id, role, name, email, password_hash, created_at, updated_at)
VALUES (:id, :company_id, :role, :name, :email, :password_hash, :created_at, :updated_at)`

	_, err := r.db.NamedExecContext(ctx, query, user)
	return err
}

func (r *Repository) ListUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error) {
	baseQuery := `FROM users`
	var args []any
	var clauses []string

	if filter.CompanyID != nil {
		clauses = append(clauses, fmt.Sprintf("company_id = $%d", len(args)+1))
		args = append(args, *filter.CompanyID)
	}

	where := ""
	if len(clauses) > 0 {
		where = " WHERE " + strings.Join(clauses, " AND ")
	}

	countQuery := "SELECT count(*) " + baseQuery + where
	var total int64
	if err := r.db.GetContext(ctx, &total, countQuery, args...); err != nil {
		return nil, 0, err
	}

	args = append(args, filter.Limit, filter.Offset)
	listQuery := fmt.Sprintf("SELECT id, company_id, role, name, email, password_hash, created_at, updated_at %s%s ORDER BY created_at DESC LIMIT $%d OFFSET $%d", baseQuery, where, len(args)-1, len(args))

	var users []domain.User
	if err := r.db.SelectContext(ctx, &users, listQuery, args...); err != nil {
		return nil, 0, err
	}

	return users, total, nil
}

func (r *Repository) GetUser(ctx context.Context, id uuid.UUID) (*domain.User, error) {
	var user domain.User
	query := `SELECT id, company_id, role, name, email, password_hash, created_at, updated_at FROM users WHERE id = $1`
	if err := r.db.GetContext(ctx, &user, query, id); err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *Repository) GetUserByEmail(ctx context.Context, email string) (*domain.User, error) {
	var user domain.User
	query := `SELECT id, company_id, role, name, email, password_hash, created_at, updated_at FROM users WHERE email = $1`
	if err := r.db.GetContext(ctx, &user, query, email); err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *Repository) UpdateUser(ctx context.Context, user *domain.User) error {
	user.UpdatedAt = time.Now().UTC()

	query := `UPDATE users
SET company_id = :company_id, role = :role, name = :name, email = :email, password_hash = :password_hash, updated_at = :updated_at
WHERE id = :id`

	res, err := r.db.NamedExecContext(ctx, query, user)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows == 0 {
		return errors.New("user not found")
	}
	return nil
}

func (r *Repository) DeleteUser(ctx context.Context, id uuid.UUID) error {
	res, err := r.db.ExecContext(ctx, "DELETE FROM users WHERE id = $1", id)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows == 0 {
		return errors.New("user not found")
	}
	return nil
}

func (r *Repository) AddCartItem(ctx context.Context, item *domain.CartItem) (*domain.CartItem, error) {
	now := time.Now().UTC()
	item.CreatedAt = now
	item.UpdatedAt = now

	query := `INSERT INTO cart_items (id, buyer_id, product_id, quantity, unit_cents, batch, expires_at, created_at, updated_at)
VALUES (:id, :buyer_id, :product_id, :quantity, :unit_cents, :batch, :expires_at, :created_at, :updated_at)
ON CONFLICT (buyer_id, product_id) DO UPDATE
SET quantity = cart_items.quantity + EXCLUDED.quantity,
    unit_cents = EXCLUDED.unit_cents,
    batch = EXCLUDED.batch,
    expires_at = EXCLUDED.expires_at,
    updated_at = EXCLUDED.updated_at
RETURNING id, buyer_id, product_id, quantity, unit_cents, batch, expires_at, created_at, updated_at`

	var saved domain.CartItem
	rows, err := r.db.NamedQueryContext(ctx, query, item)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	if rows.Next() {
		if err := rows.StructScan(&saved); err != nil {
			return nil, err
		}
		return &saved, nil
	}
	return nil, errors.New("failed to persist cart item")
}

func (r *Repository) ListCartItems(ctx context.Context, buyerID uuid.UUID) ([]domain.CartItem, error) {
	query := `SELECT ci.id, ci.buyer_id, ci.product_id, ci.quantity, ci.unit_cents, ci.batch, ci.expires_at, ci.created_at, ci.updated_at,
       p.name AS product_name
FROM cart_items ci
JOIN products p ON p.id = ci.product_id
WHERE ci.buyer_id = $1
ORDER BY ci.created_at DESC`

	var items []domain.CartItem
	if err := r.db.SelectContext(ctx, &items, query, buyerID); err != nil {
		return nil, err
	}
	return items, nil
}

func (r *Repository) DeleteCartItem(ctx context.Context, id uuid.UUID, buyerID uuid.UUID) error {
	res, err := r.db.ExecContext(ctx, "DELETE FROM cart_items WHERE id = $1 AND buyer_id = $2", id, buyerID)
	if err != nil {
		return err
	}
	rows, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if rows == 0 {
		return errors.New("cart item not found")
	}
	return nil
}

func (r *Repository) CreateReview(ctx context.Context, review *domain.Review) error {
	now := time.Now().UTC()
	review.CreatedAt = now

	query := `INSERT INTO reviews (id, order_id, buyer_id, seller_id, rating, comment, created_at)
VALUES (:id, :order_id, :buyer_id, :seller_id, :rating, :comment, :created_at)`

	_, err := r.db.NamedExecContext(ctx, query, review)
	return err
}

func (r *Repository) GetCompanyRating(ctx context.Context, companyID uuid.UUID) (*domain.CompanyRating, error) {
	var row struct {
		Avg   *float64 `db:"avg"`
		Count int64    `db:"count"`
	}
	query := `SELECT AVG(rating)::float AS avg, COUNT(*) AS count FROM reviews WHERE seller_id = $1`
	if err := r.db.GetContext(ctx, &row, query, companyID); err != nil {
		return nil, err
	}

	avg := 0.0
	if row.Avg != nil {
		avg = *row.Avg
	}

	return &domain.CompanyRating{CompanyID: companyID, AverageScore: avg, TotalReviews: row.Count}, nil
}

func (r *Repository) SellerDashboard(ctx context.Context, sellerID uuid.UUID) (*domain.SellerDashboard, error) {
	var sales struct {
		Total  *int64 `db:"total"`
		Orders int64  `db:"orders"`
	}

	baseStatuses := []string{string(domain.OrderStatusPaid), string(domain.OrderStatusInvoiced), string(domain.OrderStatusDelivered)}
	if err := r.db.GetContext(ctx, &sales, `SELECT COALESCE(SUM(total_cents), 0) AS total, COUNT(*) AS orders FROM orders WHERE seller_id = $1 AND status = ANY($2)`, sellerID, baseStatuses); err != nil {
		return nil, err
	}

	var topProducts []domain.TopProduct
	topQuery := `SELECT oi.product_id, p.name, SUM(oi.quantity) AS total_quantity, SUM(oi.quantity * oi.unit_cents) AS revenue_cents
FROM order_items oi
JOIN orders o ON o.id = oi.order_id
JOIN products p ON p.id = oi.product_id
WHERE o.seller_id = $1 AND o.status = ANY($2)
GROUP BY oi.product_id, p.name
ORDER BY total_quantity DESC
LIMIT 5`
	if err := r.db.SelectContext(ctx, &topProducts, topQuery, sellerID, baseStatuses); err != nil {
		return nil, err
	}

	var lowStock []domain.Product
	if err := r.db.SelectContext(ctx, &lowStock, `SELECT id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at FROM products WHERE seller_id = $1 AND stock <= 10 ORDER BY stock ASC, updated_at DESC`, sellerID); err != nil {
		return nil, err
	}

	total := int64(0)
	if sales.Total != nil {
		total = *sales.Total
	}

	return &domain.SellerDashboard{
		SellerID:        sellerID,
		TotalSalesCents: total,
		OrdersCount:     sales.Orders,
		TopProducts:     topProducts,
		LowStockAlerts:  lowStock,
	}, nil
}

func (r *Repository) AdminDashboard(ctx context.Context, since time.Time) (*domain.AdminDashboard, error) {
	var gmv *int64
	if err := r.db.GetContext(ctx, &gmv, `SELECT COALESCE(SUM(total_cents), 0) FROM orders WHERE status = ANY($1)`, []string{string(domain.OrderStatusPaid), string(domain.OrderStatusInvoiced), string(domain.OrderStatusDelivered)}); err != nil {
		return nil, err
	}

	var newCompanies int64
	if err := r.db.GetContext(ctx, &newCompanies, `SELECT COUNT(*) FROM companies WHERE created_at >= $1`, since); err != nil {
		return nil, err
	}

	totalGMV := int64(0)
	if gmv != nil {
		totalGMV = *gmv
	}

	return &domain.AdminDashboard{GMVCents: totalGMV, NewCompanies: newCompanies, WindowStartAt: since}, nil
}

// InitSchema applies a minimal schema for development environments.
func (r *Repository) InitSchema(ctx context.Context) error {
	schema := `
CREATE TABLE IF NOT EXISTS companies (
    id UUID PRIMARY KEY,
    role TEXT NOT NULL,
    cnpj TEXT NOT NULL UNIQUE,
    corporate_name TEXT NOT NULL,
    license_number TEXT NOT NULL,
    is_verified BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL
);

CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY,
    company_id UUID NOT NULL REFERENCES companies(id),
    role TEXT NOT NULL,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    password_hash TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL
);

CREATE TABLE IF NOT EXISTS products (
    id UUID PRIMARY KEY,
    seller_id UUID NOT NULL REFERENCES companies(id),
    name TEXT NOT NULL,
    description TEXT,
    batch TEXT NOT NULL,
    expires_at DATE NOT NULL,
    price_cents BIGINT NOT NULL,
    stock BIGINT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL
);

CREATE TABLE IF NOT EXISTS inventory_adjustments (
    id UUID PRIMARY KEY,
    product_id UUID NOT NULL REFERENCES products(id),
    delta BIGINT NOT NULL,
    reason TEXT,
    created_at TIMESTAMPTZ NOT NULL
);

CREATE TABLE IF NOT EXISTS orders (
    id UUID PRIMARY KEY,
    buyer_id UUID NOT NULL REFERENCES companies(id),
    seller_id UUID NOT NULL REFERENCES companies(id),
    status TEXT NOT NULL,
    total_cents BIGINT NOT NULL,
    shipping_recipient_name TEXT,
    shipping_street TEXT,
    shipping_number TEXT,
    shipping_complement TEXT,
    shipping_district TEXT,
    shipping_city TEXT,
    shipping_state TEXT,
    shipping_zip_code TEXT,
    shipping_country TEXT,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL
);

CREATE TABLE IF NOT EXISTS order_items (
    id UUID PRIMARY KEY,
    order_id UUID NOT NULL REFERENCES orders(id),
    product_id UUID NOT NULL REFERENCES products(id),
    quantity BIGINT NOT NULL,
    unit_cents BIGINT NOT NULL,
    batch TEXT NOT NULL,
    expires_at DATE NOT NULL
);

CREATE TABLE IF NOT EXISTS cart_items (
    id UUID PRIMARY KEY,
    buyer_id UUID NOT NULL REFERENCES companies(id),
    product_id UUID NOT NULL REFERENCES products(id),
    quantity BIGINT NOT NULL,
    unit_cents BIGINT NOT NULL,
    batch TEXT,
    expires_at DATE,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL,
    UNIQUE (buyer_id, product_id)
);

CREATE TABLE IF NOT EXISTS reviews (
    id UUID PRIMARY KEY,
    order_id UUID NOT NULL UNIQUE REFERENCES orders(id),
    buyer_id UUID NOT NULL REFERENCES companies(id),
    seller_id UUID NOT NULL REFERENCES companies(id),
    rating INT NOT NULL CHECK (rating BETWEEN 1 AND 5),
    comment TEXT,
    created_at TIMESTAMPTZ NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_reviews_seller_id ON reviews (seller_id);

CREATE TABLE IF NOT EXISTS shipments (
    id UUID PRIMARY KEY,
    order_id UUID NOT NULL UNIQUE REFERENCES orders(id),
    carrier TEXT NOT NULL,
    tracking_code TEXT,
    external_tracking TEXT,
    status TEXT NOT NULL,
    created_at TIMESTAMPTZ NOT NULL,
    updated_at TIMESTAMPTZ NOT NULL
);
`

	if _, err := r.db.ExecContext(ctx, schema); err != nil {
		return fmt.Errorf("apply schema: %w", err)
	}
	return nil
}
