package reports

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

	"github.com/shopspring/decimal"

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/pkg/httpresponse"
	"github.com/TaruLab/keluargamart-backend/pkg/logger"
	"github.com/TaruLab/keluargamart-backend/pkg/middleware"
)

// OutletDashboard is the kepala toko cockpit payload for GET /dashboard/outlet:
// every figure is scoped to the caller's own outlet (store_users →
// stores.warehouse_id). Company-wide /dashboard/kpis stays for GM/owner.
type OutletDashboard struct {
	Store           OutletStoreRef
	TodayRevenue    decimal.Decimal
	TodayCount      int
	MonthRevenue    decimal.Decimal
	MonthCount      int
	OfflineMonthRev decimal.Decimal
	OnlineMonthRev  decimal.Decimal
	MonthRevChange  *float64
	StockValue      decimal.Decimal
	LowStockCount   int
	OutOfStockCount int
	TotalProducts   int
	TopProducts     []OutletTopProduct
	RecentMovements []OutletMovement
	LowStockList    []OutletLowStock
}

type OutletStoreRef struct {
	ID   int64  `json:"id"`
	Code string `json:"code"`
	Name string `json:"name"`
}

type OutletTopProduct struct {
	ID       int64           `json:"id"`
	Code     string          `json:"code"`
	Name     string          `json:"name"`
	Quantity decimal.Decimal `json:"quantity"`
	Revenue  decimal.Decimal `json:"revenue"`
}

type OutletMovement struct {
	MovementDate    string          `json:"movement_date"`
	ProductName     string          `json:"product_name"`
	MovementType    string          `json:"movement_type"`
	Direction       string          `json:"direction"`
	Quantity        decimal.Decimal `json:"quantity"`
	ReferenceNumber string          `json:"reference_number"`
}

type OutletLowStock struct {
	ID       int64           `json:"id"`
	Code     string          `json:"code"`
	Name     string          `json:"name"`
	Quantity decimal.Decimal `json:"quantity"`
	MinStock decimal.Decimal `json:"min_stock"`
	Unit     string          `json:"unit"`
}

// OutletDashboard serves GET /dashboard/outlet. Optional repository seam (same
// pattern as POSDashboard) so test fakes keep working without it.
func (h *Handler) OutletDashboard(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFrom(r.Context())
	sc := auth.StoreScopeFrom(r.Context())
	if sc.PlatformWide() {
		// A platform-wide caller has no single outlet — nothing to scope to.
		httpresponse.OK(w, http.StatusOK, rid, map[string]any{})
		return
	}
	ex, ok := h.svc.store.(interface {
		OutletDashboard(ctx context.Context, storeIDs []int64, now time.Time) (*OutletDashboard, error)
	})
	if !ok {
		httpresponse.OK(w, http.StatusOK, rid, map[string]any{})
		return
	}
	d, err := ex.OutletDashboard(r.Context(), sc.IDs(), time.Now().In(h.svc.loc))
	if err != nil {
		httpresponse.Internal(w, rid, logger.FromContext(r.Context()), err)
		return
	}
	httpresponse.OK(w, http.StatusOK, rid, map[string]any{
		"store": d.Store,
		"sales": map[string]any{
			"todayRevenue":        d.TodayRevenue,
			"todayCount":          d.TodayCount,
			"monthRevenue":        d.MonthRevenue,
			"monthCount":          d.MonthCount,
			"offlineMonthRevenue": d.OfflineMonthRev,
			"onlineMonthRevenue":  d.OnlineMonthRev,
			"monthRevenueChange":  d.MonthRevChange,
		},
		"inventory": map[string]any{
			"stockValue":      d.StockValue,
			"lowStockCount":   d.LowStockCount,
			"outOfStockCount": d.OutOfStockCount,
			"totalProducts":   d.TotalProducts,
		},
		"topProducts":     d.TopProducts,
		"recentMovements": d.RecentMovements,
		"lowStockList":    d.LowStockList,
	})
}

// OutletDashboard (repository) computes the cockpit payload for the given
// tenant store ids. Warehouses are resolved from stores.warehouse_id.
func (r *Repository) OutletDashboard(ctx context.Context, storeIDs []int64, now time.Time) (*OutletDashboard, error) {
	d := &OutletDashboard{}
	if len(storeIDs) == 0 {
		return d, nil
	}

	warehouseIDs, err := r.q.WarehousesForStores(ctx, storeIDs)
	if err != nil {
		return nil, err
	}
	if len(warehouseIDs) == 0 {
		warehouseIDs = []int64{0} // keeps ANY($n) valid; matches nothing
	}

	_ = r.pool.QueryRow(ctx, `
SELECT id, code, name FROM stores WHERE id = ANY($1) ORDER BY id LIMIT 1`,
		storeIDs).Scan(&d.Store.ID, &d.Store.Code, &d.Store.Name)

	monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
	prevStart := monthStart.AddDate(0, -1, 0)
	today := now.Format("2006-01-02")

	// Sales aggregates for the outlet (POS = offline, ecommerce = online).
	var prevRev decimal.Decimal
	if err := r.pool.QueryRow(ctx, `
SELECT
  COALESCE(SUM(total_amount) FILTER (WHERE order_date = $2), 0),
  COUNT(*)                    FILTER (WHERE order_date = $2),
  COALESCE(SUM(total_amount) FILTER (WHERE order_date >= $3), 0),
  COUNT(*)                    FILTER (WHERE order_date >= $3),
  COALESCE(SUM(total_amount) FILTER (WHERE order_date >= $3 AND order_type <> 'ecommerce'), 0),
  COALESCE(SUM(total_amount) FILTER (WHERE order_date >= $3 AND order_type =  'ecommerce'), 0),
  COALESCE(SUM(total_amount) FILTER (WHERE order_date >= $4 AND order_date < $3), 0)
FROM sales_orders
WHERE store_id = ANY($1) AND status <> 'cancelled'`,
		storeIDs, today, monthStart.Format("2006-01-02"), prevStart.Format("2006-01-02"),
	).Scan(&d.TodayRevenue, &d.TodayCount, &d.MonthRevenue, &d.MonthCount,
		&d.OfflineMonthRev, &d.OnlineMonthRev, &prevRev); err != nil {
		return nil, err
	}
	if prevRev.Sign() > 0 {
		cur, _ := d.MonthRevenue.Float64()
		prv, _ := prevRev.Float64()
		v := (cur - prv) / prv * 100
		d.MonthRevChange = &v
	}

	// Inventory snapshot for the outlet warehouse.
	if err := r.pool.QueryRow(ctx, `
SELECT
  COALESCE(SUM(s.quantity * p.purchase_price), 0),
  COUNT(*) FILTER (WHERE s.quantity > 0 AND s.quantity < p.min_stock),
  COUNT(*) FILTER (WHERE s.quantity <= 0),
  COUNT(DISTINCT s.product_id)
FROM stocks s JOIN products p ON p.id = s.product_id
WHERE s.warehouse_id = ANY($1)`,
		warehouseIDs).Scan(&d.StockValue, &d.LowStockCount, &d.OutOfStockCount, &d.TotalProducts); err != nil {
		return nil, err
	}

	// Top products this month (by quantity sold at the outlet).
	topRows, err := r.pool.Query(ctx, `
SELECT p.id, p.code, p.name, COALESCE(SUM(i.quantity), 0) AS qty,
       COALESCE(SUM(i.subtotal), 0) AS revenue
FROM sales_order_items i
JOIN sales_orders o ON o.id = i.sales_order_id
JOIN products p ON p.id = i.product_id
WHERE o.store_id = ANY($1) AND o.status <> 'cancelled' AND o.order_date >= $2
GROUP BY p.id, p.code, p.name
ORDER BY qty DESC
LIMIT 8`, storeIDs, monthStart.Format("2006-01-02"))
	if err != nil {
		return nil, err
	}
	defer topRows.Close()
	for topRows.Next() {
		var tp OutletTopProduct
		if err := topRows.Scan(&tp.ID, &tp.Code, &tp.Name, &tp.Quantity, &tp.Revenue); err != nil {
			return nil, err
		}
		d.TopProducts = append(d.TopProducts, tp)
	}

	// Recent stock movements at the outlet warehouse.
	movRows, err := r.pool.Query(ctx, `
SELECT m.created_at::date::text, COALESCE(p.name, ''), m.movement_type,
       CASE WHEN m.quantity_after < m.quantity_before THEN 'out' ELSE 'in' END,
       m.quantity, COALESCE(m.movement_number, '')
FROM stock_movements m
LEFT JOIN products p ON p.id = m.product_id
WHERE m.warehouse_id = ANY($1)
ORDER BY m.id DESC
LIMIT 8`, warehouseIDs)
	if err != nil {
		return nil, err
	}
	defer movRows.Close()
	for movRows.Next() {
		var mv OutletMovement
		if err := movRows.Scan(&mv.MovementDate, &mv.ProductName, &mv.MovementType, &mv.Direction, &mv.Quantity, &mv.ReferenceNumber); err != nil {
			return nil, err
		}
		d.RecentMovements = append(d.RecentMovements, mv)
	}

	// Low-stock list for the outlet warehouse.
	lowRows, err := r.pool.Query(ctx, `
SELECT p.id, p.code, p.name, s.quantity, p.min_stock, COALESCE(u.symbol, '')
FROM stocks s
JOIN products p ON p.id = s.product_id
LEFT JOIN units u ON u.id = p.unit_id
WHERE s.warehouse_id = ANY($1) AND s.quantity < p.min_stock
ORDER BY s.quantity ASC
LIMIT 10`, warehouseIDs)
	if err != nil {
		return nil, err
	}
	defer lowRows.Close()
	for lowRows.Next() {
		var ls OutletLowStock
		if err := lowRows.Scan(&ls.ID, &ls.Code, &ls.Name, &ls.Quantity, &ls.MinStock, &ls.Unit); err != nil {
			return nil, err
		}
		d.LowStockList = append(d.LowStockList, ls)
	}

	return d, nil
}
