package mysql

import (
	"context"
	"strings"

	"gorm.io/gorm"

	"finance-portal/model"
)

type cloudCollection struct {
	db *gorm.DB
}

func newCloudCollection(ds *datastore) *cloudCollection {
	return &cloudCollection{db: ds.db}
}

func (c *cloudCollection) List(_ context.Context) (records []model.CloudCollection, err error) {
	err = c.db.Find(&records).Error
	return
}

func (c *cloudCollection) ListByMonth(_ context.Context, billMonth string, sync int) (records []*model.CloudCollection, err error) {
	tx := c.db.Where("bill_month = ?", billMonth)
	if sync != -1 {
		tx = tx.Where("sync = ?", sync)
	}
	err = tx.Find(&records).Error
	return
}

func (c *cloudCollection) GetByID(_ context.Context, id uint64) (record model.CloudCollection, err error) {
	err = c.db.Where("id = ?", id).First(&record).Error
	return
}

func (c *cloudCollection) Update(_ context.Context, record *model.CloudCollection) error {
	return c.db.Save(record).Error
}

func (c *cloudCollection) DeleteAndSave(ctx context.Context, records []model.CloudCollection) ([]model.CloudCollection, error) {
	if len(records) == 0 {
		return nil, nil
	}

	// 获取当前月份
	billMonth := records[0].BillMonth

	// 开启事务
	tx := c.db.Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()

	// 1. 批量删除当前月份的所有记录
	if err := tx.Where("bill_month = ? and (sync = 0 or sync = null) and (row_lock = null or row_lock = false)", billMonth).Unscoped().Delete(&model.CloudCollection{}).Error; err != nil {
		tx.Rollback()
		return nil, err
	}

	// 2. 批量查询哪些记录已经存在
	var existingRecords []model.CloudCollection
	conditions := make([]string, 0, len(records))
	args := make([]interface{}, 0, len(records)*3+1)
	args = append(args, billMonth)
	for _, record := range records {
		conditions = append(conditions, "(adjust_tenant_id = ? AND invoice_number = ?)")
		args = append(args, record.AdjustTenantID, record.InvoiceNumber)
	}
	query := tx.Where("bill_month = ? and ("+strings.Join(conditions, " OR ")+")", args...)
	if len(conditions) == 0 {
		query = tx.Where("bill_month = ?", billMonth)
	}
	if err := query.Unscoped().Find(&existingRecords).Error; err != nil {
		tx.Rollback()
		return nil, err
	}

	// 3. 构建需要插入或更新的记录
	existingByInvoice := make(map[string][]model.CloudCollection)
	for _, record := range existingRecords {
		existingByInvoice[cloudCollectionInvoiceKey(record)] = append(existingByInvoice[cloudCollectionInvoiceKey(record)], record)
	}

	var recordsToInsert []model.CloudCollection
	changedRecords := make([]model.CloudCollection, 0)
	for _, record := range records {
		existingRecordsForInvoice := existingByInvoice[cloudCollectionInvoiceKey(record)]
		// A synced row that was explicitly soft-deleted is a tombstone.  Do not
		// resurrect it from the automatic monthly source; doing so would also
		// conflict with the active-key unique index.
		if hasPreservedDeletedCloudCollectionExactKey(record, existingRecordsForInvoice) {
			continue
		}
		existing, exists := findActiveCloudCollectionForUpsert(record, existingRecordsForInvoice)
		for _, stale := range staleCloudCollectionsForInvoice(record, existingRecordsForInvoice) {
			if err := tx.Delete(&stale).Error; err != nil {
				tx.Rollback()
				return nil, err
			}
			changedRecords = append(changedRecords, stale)
		}
		if exists {
			merged, changed := mergeCloudCollectionForUpsert(existing, record)
			if !changed {
				continue
			}
			if err := tx.Model(&model.CloudCollection{}).
				Where("id = ?", merged.ID).
				Updates(cloudCollectionUpsertUpdates(merged)).Error; err != nil {
				tx.Rollback()
				return nil, err
			}
			changedRecords = append(changedRecords, merged)
		} else {
			recordsToInsert = append(recordsToInsert, record)
		}
	}

	// 4. 批量插入新记录（分批次插入）
	if len(recordsToInsert) > 0 {
		batchSize := 1000 // 每批次插入 1000 条记录
		for i := 0; i < len(recordsToInsert); i += batchSize {
			end := i + batchSize
			if end > len(recordsToInsert) {
				end = len(recordsToInsert)
			}
			batch := recordsToInsert[i:end]
			if err := tx.Create(&batch).Error; err != nil {
				tx.Rollback()
				return nil, err
			}
			changedRecords = append(changedRecords, batch...)
		}
	}

	// 提交事务
	if err := tx.Commit().Error; err != nil {
		tx.Rollback()
		return nil, err
	}

	return changedRecords, nil
}

func cloudCollectionUpsertUpdates(record model.CloudCollection) map[string]interface{} {
	return map[string]interface{}{
		"payable_amount":        record.PayableAmount,
		"payable_date":          record.PayableDate,
		"actual_billing_date":   record.ActualBillingDate,
		"collections_date":      record.CollectionsDate,
		"collection_status":     record.CollectionStatus,
		"collections_amount":    record.CollectionsAmount,
		"files":                 record.Files,
		"invoice_status":        record.InvoiceStatus,
		"order":                 record.Order,
		"order_number":          record.OrderNumber,
		"account_number":        record.AccountNumber,
		"account_name":          record.AccountName,
		"account_surrogate_key": record.AccountSurrogateKey,
		"account_region":        record.AccountRegion,
		"is_create_invoice":     record.IsCreateInvoice,
		"stripe_paid":           record.StripePaid,
		"type":                  record.Type,
		"stripe_tax_rate":       record.StripeTaxRate,
		"collected_date":        record.CollectedDate,
		"collection_type":       record.CollectionType,
		"file_content":          record.FileContent,
		"updated_at":            record.UpdatedAt,
	}
}

func cloudCollectionInvoiceKey(record model.CloudCollection) string {
	return strings.Join([]string{
		strings.TrimSpace(record.BillMonth),
		strings.TrimSpace(record.AdjustTenantID),
		strings.TrimSpace(record.InvoiceNumber),
	}, "|")
}

func cloudCollectionExactKey(record model.CloudCollection) model.CloudCollectionKey {
	return model.CloudCollectionKey{
		BillMonth:      strings.TrimSpace(record.BillMonth),
		AdjustTenantID: strings.TrimSpace(record.AdjustTenantID),
		InvoiceNumber:  strings.TrimSpace(record.InvoiceNumber),
		Type:           record.Type,
	}
}

func findActiveCloudCollectionForUpsert(incoming model.CloudCollection, records []model.CloudCollection) (model.CloudCollection, bool) {
	incomingKey := cloudCollectionExactKey(incoming)
	for _, record := range records {
		if record.DeletedAt.Valid || record.RowLock {
			continue
		}
		if cloudCollectionExactKey(record) == incomingKey {
			return record, true
		}
	}
	return model.CloudCollection{}, false
}

func hasPreservedDeletedCloudCollectionExactKey(incoming model.CloudCollection, records []model.CloudCollection) bool {
	incomingKey := cloudCollectionExactKey(incoming)
	for _, record := range records {
		if record.DeletedAt.Valid && record.Sync == 1 && cloudCollectionExactKey(record) == incomingKey {
			return true
		}
	}
	return false
}

func staleCloudCollectionsForInvoice(incoming model.CloudCollection, records []model.CloudCollection) []model.CloudCollection {
	incomingKey := cloudCollectionExactKey(incoming)
	staleRecords := make([]model.CloudCollection, 0)
	for _, record := range records {
		if record.DeletedAt.Valid || record.RowLock {
			continue
		}
		if cloudCollectionExactKey(record) != incomingKey {
			staleRecords = append(staleRecords, record)
		}
	}
	return staleRecords
}

func mergeCloudCollectionForUpsert(existing, incoming model.CloudCollection) (model.CloudCollection, bool) {
	if existing.RowLock {
		return existing, false
	}

	merged := existing
	merged.PayableAmount = incoming.PayableAmount
	merged.PayableDate = incoming.PayableDate
	merged.ActualBillingDate = incoming.ActualBillingDate
	merged.CollectionsDate = incoming.CollectionsDate
	merged.CollectionStatus = incoming.CollectionStatus
	merged.CollectionsAmount = incoming.CollectionsAmount
	merged.Files = incoming.Files
	merged.InvoiceStatus = incoming.InvoiceStatus
	merged.Order = mergeCloudCollectionOrderRef(existing.Order, incoming.Order)
	merged.OrderNumber = mergeCloudCollectionOrderRef(existing.OrderNumber, incoming.OrderNumber)
	merged.AccountNumber = incoming.AccountNumber
	merged.AccountName = incoming.AccountName
	merged.AccountSurrogateKey = incoming.AccountSurrogateKey
	merged.AccountRegion = incoming.AccountRegion
	merged.IsCreateInvoice = incoming.IsCreateInvoice
	merged.StripePaid = incoming.StripePaid
	merged.Type = incoming.Type
	merged.StripeTaxRate = incoming.StripeTaxRate
	merged.CollectedDate = incoming.CollectedDate
	merged.CollectionType = incoming.CollectionType
	if strings.TrimSpace(incoming.FileContent) != "" {
		merged.FileContent = incoming.FileContent
	}
	merged.UpdatedAt = incoming.UpdatedAt
	merged.Sync = existing.Sync
	merged.SyncTime = existing.SyncTime
	merged.CollectionNumber = existing.CollectionNumber
	merged.CollectionID = existing.CollectionID
	merged.RowLock = existing.RowLock

	return merged, merged != existing
}

func mergeCloudCollectionOrderRef(existing, incoming string) string {
	existing = strings.TrimSpace(existing)
	incoming = strings.TrimSpace(incoming)
	if incoming == "" {
		return existing
	}
	if existing == "" || isGeneratedCloudCollectionOrderRef(existing) {
		return incoming
	}
	if isGeneratedCloudCollectionOrderRef(incoming) {
		return existing
	}
	return incoming
}

func isGeneratedCloudCollectionOrderRef(value string) bool {
	return strings.Contains(strings.TrimSpace(value), "__ON_DEMAND__")
}

func (c *cloudCollection) ListUncollected(ctx context.Context) (records []*model.CloudCollection, err error) {
	err = c.db.Where("collection_status = ?", "Uncollected").Find(&records).Error

	return
}

func (c *cloudCollection) UpdateFields(_ context.Context, id uint64, updates map[string]interface{}) error {
	return c.db.Model(&model.CloudCollection{}).Where("id = ?", id).Updates(updates).Error
}
