package exporter

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

	"gorm.io/gorm"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
)

const (
	orderExportBaseHeaderCount = 11
	orderExportCardGroupSize   = 2
	orderExportMinCardGroups   = 1
)

// OrderDataSource 订单导出数据源。
type OrderDataSource struct {
	db *gorm.DB
}

// NewOrderDataSource 创建订单导出数据源。
func NewOrderDataSource(db *gorm.DB) *OrderDataSource {
	return &OrderDataSource{db: db}
}

// Scene 返回导出场景编码。
func (s *OrderDataSource) Scene() string {
	return constants.ExportTaskSceneOrder
}

// Count 统计订单导出行数。
func (s *OrderDataSource) Count(ctx context.Context, params ExportParams) (int, error) {
	query, err := s.applyFilters(ctx, s.baseQuery(ctx), params)
	if err != nil {
		return 0, err
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		return 0, err
	}
	return int(total), nil
}

// Headers 返回订单导出表头。
func (s *OrderDataSource) Headers(ctx context.Context, params ExportParams) ([]string, error) {
	cardGroups, err := s.resolveCardGroupCount(ctx, params)
	if err != nil {
		return nil, err
	}
	return buildOrderExportHeaders(cardGroups), nil
}

// Fetch 按 offset/limit 查询订单导出数据。
func (s *OrderDataSource) Fetch(ctx context.Context, params ExportParams, offset, limit int) ([][]string, error) {
	if limit <= 0 {
		return [][]string{}, nil
	}

	cardGroups := orderCardGroupCountFromHeaders(params.ResolvedHeaders)
	if cardGroups <= 0 {
		resolved, err := s.resolveCardGroupCount(ctx, params)
		if err != nil {
			return nil, err
		}
		cardGroups = resolved
	}

	orders, err := s.fetchOrders(ctx, params, offset, limit)
	if err != nil {
		return nil, err
	}
	if len(orders) == 0 {
		return [][]string{}, nil
	}

	orderIDs := make([]uint, 0, len(orders))
	deviceIDs := make([]uint, 0, len(orders))
	for _, item := range orders {
		orderIDs = append(orderIDs, item.ID)
		if item.DeviceID != nil {
			deviceIDs = append(deviceIDs, *item.DeviceID)
		}
	}

	itemMap, err := s.fetchOrderItems(ctx, orderIDs)
	if err != nil {
		return nil, err
	}
	cardMap, err := s.fetchBoundCards(ctx, deviceIDs)
	if err != nil {
		return nil, err
	}
	paymentMap, err := s.fetchPreferredPayments(ctx, orderIDs)
	if err != nil {
		return nil, err
	}

	rows := make([][]string, 0, len(orders))
	for _, item := range orders {
		var cards []orderExportCardRow
		if item.DeviceID != nil {
			cards = cardMap[*item.DeviceID]
		}
		rows = append(rows, buildOrderExportRow(item, itemMap[item.ID], paymentMap[item.ID], cards, cardGroups))
	}
	return rows, nil
}

func (s *OrderDataSource) baseQuery(ctx context.Context) *gorm.DB {
	return s.db.WithContext(ctx).
		Table("tb_order AS o").
		Where("o.deleted_at IS NULL")
}

// applyFilters 应用与 /api/admin/orders 当前实际行为一致的筛选条件。
// is_expired 在接口 Service 中未进入 Store 筛选，这里按当前接口行为忽略。
func (s *OrderDataSource) applyFilters(ctx context.Context, query *gorm.DB, params ExportParams) (*gorm.DB, error) {
	query = s.applyOrderScope(query, params)

	if params.UserType == constants.UserTypeAgent {
		if params.CreatorShopID != nil {
			query = query.Where("o.buyer_type = ? AND o.buyer_id = ?", model.BuyerTypeAgent, *params.CreatorShopID)
		} else {
			query = query.Where("o.buyer_type = ?", model.BuyerTypeAgent)
		}
	}
	if paymentStatus, ok := filterInt(params.Filters, "payment_status"); ok {
		query = query.Where("o.payment_status = ?", paymentStatus)
	}
	if paymentMethod, ok := filterString(params.Filters, "payment_method"); ok {
		query = query.Where("o.payment_method = ?", paymentMethod)
	}
	if orderType, ok := filterString(params.Filters, "order_type"); ok {
		query = query.Where("o.order_type = ?", orderType)
	}
	if orderNo, ok := filterString(params.Filters, "order_no"); ok {
		query = query.Where("o.order_no = ?", orderNo)
	}
	if purchaseRole, ok := filterString(params.Filters, "purchase_role"); ok {
		query = query.Where("o.purchase_role = ?", purchaseRole)
	}
	if sellerShopID, ok := filterUint(params.Filters, "seller_shop_id"); ok {
		query = query.Where("o.seller_shop_id = ?", sellerShopID)
	}
	if start, ok := filterTime(params.Filters, "start_time"); ok {
		query = query.Where("o.created_at >= ?", start)
	}
	if end, ok := filterTime(params.Filters, "end_time"); ok {
		query = query.Where("o.created_at <= ?", end)
	}
	if buyerPhone, ok := filterString(params.Filters, "buyer_phone"); ok {
		query = query.Where("o.buyer_phone = ?", buyerPhone)
	}
	if identifier, ok := filterString(params.Filters, "identifier"); ok {
		iotCardID, deviceID, err := s.resolveOrderListAssetIdentifier(ctx, params, identifier)
		if err != nil {
			return nil, err
		}
		assetQuery := s.db.Where("o.asset_identifier = ?", identifier)
		if iotCardID != nil {
			assetQuery = assetQuery.Or("o.iot_card_id = ?", *iotCardID)
		}
		if deviceID != nil {
			assetQuery = assetQuery.Or("o.device_id = ?", *deviceID)
		}
		query = query.Where(assetQuery)
	}
	return query, nil
}

func (s *OrderDataSource) applyOrderScope(query *gorm.DB, params ExportParams) *gorm.DB {
	switch params.UserType {
	case constants.UserTypeSuperAdmin, constants.UserTypePlatform:
		return query
	case constants.UserTypeAgent:
		if len(params.ScopeShopIDs) == 0 {
			return query.Where("1 = 0")
		}
		return query.Where(
			s.db.Where("o.buyer_type = ? AND o.buyer_id IN ?", model.BuyerTypeAgent, params.ScopeShopIDs).
				Or("o.seller_shop_id IN ?", params.ScopeShopIDs).
				Or(s.db.Where("o.seller_shop_id IS NULL AND o.operator_type = ? AND o.operator_id IN ?", model.OperatorAccountTypeAgent, params.ScopeShopIDs)),
		)
	default:
		return query.Where("1 = 0")
	}
}

func (s *OrderDataSource) resolveCardGroupCount(ctx context.Context, params ExportParams) (int, error) {
	filtered, err := s.applyFilters(ctx, s.baseQuery(ctx), params)
	if err != nil {
		return 0, err
	}

	var count int64
	query := filtered.
		Select("COALESCE(MAX(bound_cards.card_count), 0)").
		Joins(`
			LEFT JOIN LATERAL (
				SELECT COUNT(*) AS card_count
				FROM tb_device_sim_binding AS b
				WHERE b.device_id = o.device_id
					AND b.bind_status = ?
					AND b.deleted_at IS NULL
			) AS bound_cards ON TRUE
		`, constants.BindStatusBound)
	if err := query.Scan(&count).Error; err != nil {
		return 0, err
	}
	if count < orderExportMinCardGroups {
		return orderExportMinCardGroups, nil
	}
	return int(count), nil
}

func (s *OrderDataSource) fetchOrders(ctx context.Context, params ExportParams, offset, limit int) ([]orderExportRow, error) {
	var orders []orderExportRow
	filtered, err := s.applyFilters(ctx, s.baseQuery(ctx), params)
	if err != nil {
		return nil, err
	}

	query := filtered.
		Joins("LEFT JOIN tb_shop AS sh ON sh.id = o.seller_shop_id").
		Select(`
			o.id,
			o.order_no,
			o.asset_identifier,
			o.seller_cost_price,
			o.total_amount,
			o.actual_paid_amount,
			o.payment_status,
			o.payment_method,
			o.created_at,
			o.device_id,
			COALESCE(sh.shop_name, '') AS shop_name
		`).
		Order("o.id DESC").
		Limit(limit).
		Offset(offset)
	if err := query.Scan(&orders).Error; err != nil {
		return nil, err
	}
	return orders, nil
}

func (s *OrderDataSource) fetchOrderItems(ctx context.Context, orderIDs []uint) (map[uint][]orderExportItemRow, error) {
	result := make(map[uint][]orderExportItemRow, len(orderIDs))
	if len(orderIDs) == 0 {
		return result, nil
	}

	var items []orderExportItemRow
	if err := s.db.WithContext(ctx).
		Table("tb_order_item").
		Select("order_id, package_name, quantity").
		Where("order_id IN ?", orderIDs).
		Where("deleted_at IS NULL").
		Order("order_id ASC, id ASC").
		Scan(&items).Error; err != nil {
		return nil, err
	}

	for _, item := range items {
		result[item.OrderID] = append(result[item.OrderID], item)
	}
	return result, nil
}

func (s *OrderDataSource) fetchBoundCards(ctx context.Context, deviceIDs []uint) (map[uint][]orderExportCardRow, error) {
	result := make(map[uint][]orderExportCardRow, len(deviceIDs))
	deviceIDs = normalizeUintSlice(deviceIDs)
	if len(deviceIDs) == 0 {
		return result, nil
	}

	var cards []orderExportCardRow
	if err := s.db.WithContext(ctx).
		Table("tb_device_sim_binding AS b").
		Select(`
			b.device_id,
			b.slot_position,
			c.msisdn,
			c.iccid
		`).
		Joins("JOIN tb_iot_card AS c ON c.id = b.iot_card_id AND c.deleted_at IS NULL").
		Where("b.device_id IN ?", deviceIDs).
		Where("b.bind_status = ?", constants.BindStatusBound).
		Where("b.deleted_at IS NULL").
		Order("b.device_id ASC, b.slot_position ASC, b.id ASC").
		Scan(&cards).Error; err != nil {
		return nil, err
	}

	for _, card := range cards {
		result[card.DeviceID] = append(result[card.DeviceID], card)
	}
	return result, nil
}

func (s *OrderDataSource) fetchPreferredPayments(ctx context.Context, orderIDs []uint) (map[uint]orderExportPaymentRow, error) {
	result := make(map[uint]orderExportPaymentRow, len(orderIDs))
	if len(orderIDs) == 0 {
		return result, nil
	}

	var payments []orderExportPaymentRow
	if err := s.db.WithContext(ctx).
		Table("tb_payment").
		Select("order_id, third_party_trade_no, status").
		Where("order_id IN ?", orderIDs).
		Where("order_type = ?", model.PaymentOrderTypePackage).
		Where("deleted_at IS NULL").
		Order("order_id ASC, CASE WHEN status = 1 THEN 0 ELSE 1 END ASC, created_at DESC, id DESC").
		Scan(&payments).Error; err != nil {
		return nil, err
	}

	for _, payment := range payments {
		if _, exists := result[payment.OrderID]; exists {
			continue
		}
		result[payment.OrderID] = payment
	}
	return result, nil
}

func (s *OrderDataSource) resolveOrderListAssetIdentifier(ctx context.Context, params ExportParams, identifier string) (*uint, *uint, error) {
	if identifier == "" {
		return nil, nil, nil
	}

	var registry orderExportAssetIdentifierRow
	registryErr := s.db.WithContext(ctx).
		Table("tb_asset_identifier").
		Select("asset_type, asset_id").
		Where("identifier = ?", identifier).
		First(&registry).Error
	if registryErr != nil && registryErr != gorm.ErrRecordNotFound {
		return nil, nil, registryErr
	}
	if registryErr == nil {
		switch registry.AssetType {
		case model.AssetTypeIotCard:
			if s.assetVisible(ctx, params, "tb_iot_card", registry.AssetID) {
				cardID := registry.AssetID
				return &cardID, nil, nil
			}
		case model.AssetTypeDevice:
			if s.assetVisible(ctx, params, "tb_device", registry.AssetID) {
				deviceID := registry.AssetID
				return nil, &deviceID, nil
			}
		}
	}

	if deviceID, err := s.resolveDeviceIDByIdentifier(ctx, params, identifier); err != nil {
		return nil, nil, err
	} else if deviceID != nil {
		return nil, deviceID, nil
	}

	cardID, err := s.resolveCardIDByIdentifier(ctx, params, identifier)
	if err != nil {
		return nil, nil, err
	}
	return cardID, nil, nil
}

func (s *OrderDataSource) assetVisible(ctx context.Context, params ExportParams, table string, id uint) bool {
	var count int64
	query := s.db.WithContext(ctx).Table(table).Where("id = ? AND deleted_at IS NULL", id)
	if params.UserType == constants.UserTypeAgent {
		if len(params.ScopeShopIDs) == 0 {
			return false
		}
		query = query.Where("shop_id IN ?", params.ScopeShopIDs)
	}
	return query.Count(&count).Error == nil && count > 0
}

func (s *OrderDataSource) resolveDeviceIDByIdentifier(ctx context.Context, params ExportParams, identifier string) (*uint, error) {
	var row struct {
		ID uint `gorm:"column:id"`
	}
	query := s.db.WithContext(ctx).
		Table("tb_device").
		Select("id").
		Where("deleted_at IS NULL").
		Where("virtual_no = ? OR imei = ? OR sn = ?", identifier, identifier, identifier)
	if params.UserType == constants.UserTypeAgent {
		if len(params.ScopeShopIDs) == 0 {
			return nil, nil
		}
		query = query.Where("shop_id IN ?", params.ScopeShopIDs)
	}
	err := query.First(&row).Error
	if err == gorm.ErrRecordNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &row.ID, nil
}

func (s *OrderDataSource) resolveCardIDByIdentifier(ctx context.Context, params ExportParams, identifier string) (*uint, error) {
	conditions := []string{"virtual_no = ?", "msisdn = ?", "iccid = ?"}
	args := []any{identifier, identifier, identifier}
	if len(identifier) == 19 {
		conditions = append(conditions, "iccid_19 = ?")
		args = append(args, identifier)
	} else if len(identifier) == 20 {
		conditions = append(conditions, "iccid_20 = ?")
		args = append(args, identifier)
	}

	var row struct {
		ID uint `gorm:"column:id"`
	}
	query := s.db.WithContext(ctx).
		Table("tb_iot_card").
		Select("id").
		Where("deleted_at IS NULL").
		Where(strings.Join(conditions, " OR "), args...)
	if params.UserType == constants.UserTypeAgent {
		if len(params.ScopeShopIDs) == 0 {
			return nil, nil
		}
		query = query.Where("shop_id IN ?", params.ScopeShopIDs)
	}
	err := query.First(&row).Error
	if err == gorm.ErrRecordNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &row.ID, nil
}

type orderExportRow struct {
	ID               uint      `gorm:"column:id"`
	OrderNo          string    `gorm:"column:order_no"`
	ShopName         string    `gorm:"column:shop_name"`
	AssetIdentifier  string    `gorm:"column:asset_identifier"`
	SellerCostPrice  int64     `gorm:"column:seller_cost_price"`
	TotalAmount      int64     `gorm:"column:total_amount"`
	ActualPaidAmount *int64    `gorm:"column:actual_paid_amount"`
	PaymentStatus    int       `gorm:"column:payment_status"`
	PaymentMethod    string    `gorm:"column:payment_method"`
	CreatedAt        time.Time `gorm:"column:created_at"`
	DeviceID         *uint     `gorm:"column:device_id"`
}

type orderExportItemRow struct {
	OrderID     uint   `gorm:"column:order_id"`
	PackageName string `gorm:"column:package_name"`
	Quantity    int    `gorm:"column:quantity"`
}

type orderExportPaymentRow struct {
	OrderID           uint   `gorm:"column:order_id"`
	ThirdPartyTradeNo string `gorm:"column:third_party_trade_no"`
	Status            int    `gorm:"column:status"`
}

type orderExportCardRow struct {
	DeviceID     uint   `gorm:"column:device_id"`
	SlotPosition int    `gorm:"column:slot_position"`
	MSISDN       string `gorm:"column:msisdn"`
	ICCID        string `gorm:"column:iccid"`
}

type orderExportAssetIdentifierRow struct {
	AssetType string `gorm:"column:asset_type"`
	AssetID   uint   `gorm:"column:asset_id"`
}

func buildOrderExportHeaders(cardGroups int) []string {
	if cardGroups < orderExportMinCardGroups {
		cardGroups = orderExportMinCardGroups
	}

	headers := []string{
		"订单号",
		"店铺",
		"虚拟号",
		"套餐名称",
		"成本价",
		"下单金额",
		"支付状态",
		"支付方式",
		"数量",
		"下单时间",
		"第三方单号",
	}
	for i := 1; i <= cardGroups; i++ {
		headers = append(headers,
			fmt.Sprintf("接入号%d", i),
			fmt.Sprintf("ICCID%d", i),
		)
	}
	return headers
}

func buildOrderExportRow(item orderExportRow, items []orderExportItemRow, payment orderExportPaymentRow, cards []orderExportCardRow, cardGroups int) []string {
	if cardGroups < orderExportMinCardGroups {
		cardGroups = orderExportMinCardGroups
	}

	row := []string{
		item.OrderNo,
		item.ShopName,
		item.AssetIdentifier,
		joinOrderPackageNames(items),
		formatMoneyYuan(item.SellerCostPrice),
		formatMoneyYuan(orderActualPaidAmount(item)),
		constants.GetOrderPaymentStatusName(item.PaymentStatus),
		formatOrderPaymentMethod(item.PaymentMethod),
		formatOrderQuantity(items),
		item.CreatedAt.Format(exportTimeLayout),
		payment.ThirdPartyTradeNo,
	}
	for i := 0; i < cardGroups; i++ {
		if i >= len(cards) {
			row = append(row, "", "")
			continue
		}
		row = append(row, cards[i].MSISDN, cards[i].ICCID)
	}
	return row
}

func orderCardGroupCountFromHeaders(headers []string) int {
	if len(headers) < orderExportBaseHeaderCount {
		return 0
	}
	cardColumnCount := len(headers) - orderExportBaseHeaderCount
	if cardColumnCount <= 0 || cardColumnCount%orderExportCardGroupSize != 0 {
		return 0
	}
	return cardColumnCount / orderExportCardGroupSize
}

func joinOrderPackageNames(items []orderExportItemRow) string {
	if len(items) == 0 {
		return ""
	}
	names := make([]string, 0, len(items))
	for _, item := range items {
		if item.PackageName != "" {
			names = append(names, item.PackageName)
		}
	}
	return strings.Join(names, ",")
}

func formatOrderQuantity(items []orderExportItemRow) string {
	if len(items) == 0 {
		return "0"
	}
	total := 0
	for _, item := range items {
		total += item.Quantity
	}
	return fmt.Sprintf("%d", total)
}

func orderActualPaidAmount(item orderExportRow) int64 {
	if item.ActualPaidAmount != nil {
		return *item.ActualPaidAmount
	}
	if item.PaymentStatus == model.PaymentStatusPaid {
		return item.TotalAmount
	}
	return 0
}

func formatOrderPaymentMethod(method string) string {
	switch method {
	case model.PaymentMethodWallet:
		return "钱包支付"
	case model.PaymentMethodWechat:
		return "微信支付"
	case model.PaymentMethodAlipay:
		return "支付宝支付"
	case model.PaymentMethodOffline:
		return "线下支付"
	default:
		return method
	}
}
