package dispute

import (
	"encoding/json"
	"errors"
	"time"

	"hfb_sys/backend/internal/auditlog"
	"hfb_sys/backend/internal/model"
	"hfb_sys/backend/internal/modules/notification"
	"hfb_sys/backend/internal/modules/wallet"
	"hfb_sys/backend/pkg/money"

	"gorm.io/datatypes"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type Repository struct {
	db *gorm.DB
}

func NewRepository(db *gorm.DB) *Repository {
	return &Repository{db: db}
}

func (r *Repository) Create(userID uint64, orderID uint64, req CreateRequest) (*DisputeDTO, error) {
	var createdID uint64
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
			return err
		}
		if order.RenterID != userID && order.OwnerID != userID {
			return ErrPermissionDenied
		}
		if order.Status == "completed" || order.Status == "cancelled" || order.Status == "closed" {
			return ErrInvalidDispute
		}
		isCheckoutDispute := order.Status == "pending_checkout_confirm" || order.Status == "pending_checkout_accept"
		var count int64
		if err := tx.Model(&model.Dispute{}).
			Where("order_id = ? AND status IN ?", order.ID, []string{"open", "processing"}).
			Count(&count).Error; err != nil {
			return err
		}
		if count > 0 {
			return ErrDisputeExists
		}

		targetID := order.OwnerID
		if userID == order.OwnerID {
			targetID = order.RenterID
		}
		evidence, err := marshalEvidence(req.EvidenceURLS)
		if err != nil {
			return err
		}
		row := model.Dispute{
			OrderID:      order.ID,
			InitiatorID:  userID,
			TargetUserID: targetID,
			Type:         disputeType(req.Type, isCheckoutDispute),
			Status:       "open",
			Description:  req.Description,
			EvidenceURLS: evidence,
		}
		if err := tx.Create(&row).Error; err != nil {
			return err
		}
		if isCheckoutDispute {
			now := time.Now()
			order.Status = "checkout_disputing"
			order.HandoffStatus = "checkout_disputed"
			order.SettlementStatus = "disputed"
			updates := map[string]any{
				"status":     "disputed",
				"updated_at": now,
			}
			if userID == order.RenterID {
				updates["renter_rejected_at"] = now
			}
			if err := tx.Model(&model.OrderCheckout{}).
				Where("order_id = ? AND status IN ?", order.ID, []string{"submitted", "countered"}).
				Updates(updates).Error; err != nil {
				return err
			}
		} else {
			order.Status = "disputing"
		}
		if err := tx.Save(&order).Error; err != nil {
			return err
		}
		disputeID := row.ID
		title := "订单进入申诉"
		content := "对方已发起申诉，请等待客服仲裁或补充沟通记录。"
		if isCheckoutDispute {
			title = "订单进入结账争议"
			content = "对方已发起结账争议，请等待客服仲裁或补充结账证据。"
		}
		if err := notification.Append(tx,
			notification.Entry{
				UserID:  targetID,
				Type:    "dispute",
				Title:   title,
				Content: content,
				BizType: "dispute",
				BizID:   &disputeID,
			},
			notification.Entry{
				UserID:  userID,
				Type:    "dispute",
				Title:   "申诉已提交",
				Content: "申诉已进入待处理状态，客服仲裁后会通知双方。",
				BizType: "dispute",
				BizID:   &disputeID,
			},
		); err != nil {
			return err
		}
		createdID = row.ID
		return nil
	})
	if err != nil {
		return nil, err
	}
	return r.FindForUser(userID, createdID)
}

func (r *Repository) ListForUser(userID uint64, page, pageSize int) (*PaginatedResult, error) {
	conditions := r.db.Model(&model.Dispute{}).Where("initiator_id = ? OR target_user_id = ?", userID, userID)
	var total int64
	if err := conditions.Count(&total).Error; err != nil {
		return nil, err
	}
	offset := (page - 1) * pageSize
	var rows []disputeRow
	err := r.baseQuery().
		Where("d.initiator_id = ? OR d.target_user_id = ?", userID, userID).
		Order("d.id DESC").
		Offset(offset).Limit(pageSize).
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	return &PaginatedResult{Items: toDTOs(rows), Total: total, Page: page, PageSize: pageSize}, nil
}

func (r *Repository) FindForUser(userID uint64, id uint64) (*DisputeDTO, error) {
	var row disputeRow
	if err := r.baseQuery().
		Where("d.id = ? AND (d.initiator_id = ? OR d.target_user_id = ?)", id, userID, userID).
		First(&row).Error; err != nil {
		return nil, err
	}
	dto := row.toDTO()
	return &dto, nil
}

func (r *Repository) ListAdmin(page, pageSize int) (*PaginatedResult, error) {
	var total int64
	if err := r.db.Model(&model.Dispute{}).Count(&total).Error; err != nil {
		return nil, err
	}
	offset := (page - 1) * pageSize
	var rows []disputeRow
	err := r.baseQuery().Order("d.id DESC").Offset(offset).Limit(pageSize).Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	return &PaginatedResult{Items: toDTOs(rows), Total: total, Page: page, PageSize: pageSize}, nil
}

func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest, meta AuditMeta) (*DisputeDTO, error) {
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var row model.Dispute
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&row, id).Error; err != nil {
			return err
		}
		if row.Status != "open" && row.Status != "processing" {
			return ErrDisputeCannotHandle
		}
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, row.OrderID).Error; err != nil {
			return err
		}
		var listing model.RentalListing
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, order.ListingID).Error; err != nil {
			return err
		}
		var account model.GameAccount
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, order.AccountID).Error; err != nil {
			return err
		}
		beforeOrderStatus := order.Status
		beforeHandoffStatus := order.HandoffStatus
		beforeSettlementStatus := order.SettlementStatus
		beforeListingStatus := listing.Status
		beforeAccountStatus := account.Status
		settlement, err := buildArbitrationSettlement(order, req)
		if err != nil {
			return err
		}
		now := time.Now()
		row.Status = "resolved"
		row.ArbitrationResult = req.Result
		row.ArbitrationRemark = req.Remark
		row.HandledBy = &adminID
		row.HandledAt = &now
		order.Status = arbitrateOrderStatus(req.Result)
		order.SettlementStatus = "arbitrated"
		order.SettledAt = &now
		order.OwnerSettledAt = &now
		if order.HandoffStatus != "cancelled" && order.HandoffStatus != "returned" {
			order.HandoffStatus = "arbitrated"
		}
		if req.Result == "order_close" {
			listing.Status = "offline"
			account.Status = "offline"
		} else if req.Result == "mark_abnormal" {
			listing.Status = "abnormal"
			account.Status = "abnormal"
		} else {
			listing.Status = "published"
			account.Status = "published"
		}
		if err := wallet.AppendEntries(tx, settlement.Entries...); err != nil {
			return err
		}
		if err := tx.Save(&row).Error; err != nil {
			return err
		}
		if err := tx.Save(&order).Error; err != nil {
			return err
		}
		if err := tx.Save(&listing).Error; err != nil {
			return err
		}
		if err := tx.Save(&account).Error; err != nil {
			return err
		}
		disputeID := row.ID
		if err := appendAuditLog(tx, adminID, "dispute.arbitrate", "dispute", row.ID, meta, map[string]any{
			"dispute_id":               row.ID,
			"order_id":                 order.ID,
			"order_no":                 order.OrderNo,
			"result":                   req.Result,
			"remark":                   req.Remark,
			"input_amount":             req.Amount,
			"renter_refund_amount":     settlement.RenterRefundAmount,
			"owner_income_amount":      settlement.OwnerIncomeAmount,
			"deposit_deduct_amount":    settlement.DepositDeductAmount,
			"before_order_status":      beforeOrderStatus,
			"after_order_status":       order.Status,
			"before_handoff_status":    beforeHandoffStatus,
			"after_handoff_status":     order.HandoffStatus,
			"before_settlement_status": beforeSettlementStatus,
			"after_settlement_status":  order.SettlementStatus,
			"before_listing_status":    beforeListingStatus,
			"after_listing_status":     listing.Status,
			"before_account_status":    beforeAccountStatus,
			"after_account_status":     account.Status,
		}); err != nil {
			return err
		}
		if err := notification.Append(tx,
			notification.Entry{
				UserID:  order.RenterID,
				Type:    "arbitration",
				Title:   "申诉仲裁已完成",
				Content: "客服已给出仲裁结果，请在订单和申诉记录中查看处理说明。",
				BizType: "dispute",
				BizID:   &disputeID,
			},
			notification.Entry{
				UserID:  order.OwnerID,
				Type:    "arbitration",
				Title:   "申诉仲裁已完成",
				Content: "客服已给出仲裁结果，请在订单和申诉记录中查看处理说明。",
				BizType: "dispute",
				BizID:   &disputeID,
			},
		); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	var row disputeRow
	if err := r.baseQuery().Where("d.id = ?", id).First(&row).Error; err != nil {
		return nil, err
	}
	dto := row.toDTO()
	return &dto, nil
}

type arbitrationSettlement struct {
	Entries             []wallet.Entry
	RenterRefundAmount  float64
	OwnerIncomeAmount   float64
	DepositDeductAmount float64
}

func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (arbitrationSettlement, error) {
	total := roundMoney(order.RentAmount + order.DepositAmount)
	ownerRentAmount := roundMoney(order.OwnerRentAmount)
	if ownerRentAmount <= 0 || ownerRentAmount > order.RentAmount {
		ownerRentAmount = roundMoney(order.RentAmount)
	}
	settlement := arbitrationSettlement{}
	orderID := order.ID
	if total > 0 {
		settlement.Entries = append(settlement.Entries, wallet.Entry{
			UserID:      order.RenterID,
			OrderID:     &orderID,
			Direction:   "out",
			Amount:      total,
			BalanceType: "frozen",
			BizType:     "arbitration_release_frozen",
			BizNo:       order.OrderNo,
			Remark:      "仲裁释放开发态模拟冻结金额",
		})
	}

	addRenterRefund := func(amount float64, remark string) {
		if amount <= 0 {
			return
		}
		settlement.RenterRefundAmount += amount
		settlement.Entries = append(settlement.Entries, wallet.Entry{
			UserID:      order.RenterID,
			OrderID:     &orderID,
			Direction:   "in",
			Amount:      amount,
			BalanceType: "available",
			BizType:     "arbitration_renter_refund",
			BizNo:       order.OrderNo,
			Remark:      remark,
		})
	}
	addOwnerIncome := func(amount float64, remark string) {
		if amount <= 0 {
			return
		}
		settlement.OwnerIncomeAmount += amount
		settlement.Entries = append(settlement.Entries, wallet.Entry{
			UserID:      order.OwnerID,
			OrderID:     &orderID,
			Direction:   "in",
			Amount:      amount,
			BalanceType: "available",
			BizType:     "arbitration_owner_income",
			BizNo:       order.OrderNo,
			Remark:      remark,
		})
	}

	switch req.Result {
	case "full_refund":
		addRenterRefund(total, "仲裁全额退款")
	case "partial_refund":
		req.Amount = roundMoney(req.Amount)
		if req.Amount <= 0 || req.Amount > total {
			return settlement, ErrInvalidDispute
		}
		addRenterRefund(req.Amount, "仲裁部分退款")
		addOwnerIncome(minMoney(total-req.Amount, ownerRentAmount+order.DepositAmount), "仲裁剩余金额结算给号主")
	case "release_deposit":
		addOwnerIncome(ownerRentAmount, "仲裁确认订单金额结算给号主")
		addRenterRefund(order.DepositAmount, "仲裁释放押金给租客")
	case "deduct_deposit", "compensate_owner":
		deductAmount := roundMoney(req.Amount)
		if deductAmount <= 0 {
			deductAmount = order.DepositAmount
		}
		if deductAmount > order.DepositAmount {
			return settlement, ErrInvalidDispute
		}
		settlement.DepositDeductAmount = deductAmount
		addOwnerIncome(ownerRentAmount+deductAmount, "仲裁订单金额及押金赔付结算给号主")
		addRenterRefund(order.DepositAmount-deductAmount, "仲裁退回剩余押金给租客")
	case "order_close":
		// Only release frozen funds. No available-balance settlement happens in development mode.
	case "mark_abnormal":
		// 标记异常只释放冻结账务，后续由客服继续线下复核。
	default:
		return settlement, ErrInvalidDispute
	}
	return settlement, nil
}

// minMoney 返回较小金额（角精度）
func minMoney(a float64, b float64) float64 {
	return money.Min(a, b)
}

// roundMoney 使用统一的角精度（0.1元）
func roundMoney(value float64) float64 {
	return money.Round(value)
}

func (r *Repository) baseQuery() *gorm.DB {
	return r.db.Table("disputes AS d").
		Select("d.*, o.order_no, a.title").
		Joins("JOIN rental_orders AS o ON o.id = d.order_id").
		Joins("JOIN game_accounts AS a ON a.id = o.account_id")
}

type disputeRow struct {
	model.Dispute
	OrderNo string
	Title   string
}

func (row disputeRow) toDTO() DisputeDTO {
	return DisputeDTO{
		ID:                row.ID,
		OrderID:           row.OrderID,
		OrderNo:           row.OrderNo,
		Title:             row.Title,
		InitiatorID:       row.InitiatorID,
		TargetUserID:      row.TargetUserID,
		Type:              row.Type,
		Status:            row.Status,
		Description:       row.Description,
		EvidenceURLS:      row.EvidenceURLS,
		ArbitrationResult: row.ArbitrationResult,
		ArbitrationRemark: row.ArbitrationRemark,
		HandledBy:         row.HandledBy,
		HandledAt:         row.HandledAt,
		CreatedAt:         row.CreatedAt,
		UpdatedAt:         row.UpdatedAt,
	}
}

func toDTOs(rows []disputeRow) []DisputeDTO {
	items := make([]DisputeDTO, 0, len(rows))
	for _, row := range rows {
		items = append(items, row.toDTO())
	}
	return items
}

func marshalEvidence(urls []string) (datatypes.JSON, error) {
	if len(urls) == 0 {
		return datatypes.JSON([]byte("[]")), nil
	}
	raw, err := json.Marshal(urls)
	return datatypes.JSON(raw), err
}

func arbitrateOrderStatus(result string) string {
	switch result {
	case "full_refund", "partial_refund", "order_close":
		return "closed"
	case "mark_abnormal":
		return "abnormal"
	default:
		return "completed"
	}
}

func disputeType(input string, isCheckoutDispute bool) string {
	if isCheckoutDispute {
		return "checkout_dispute"
	}
	return input
}

func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
	return auditlog.Append(tx, auditlog.Entry{
		ActorType: "admin",
		ActorID:   actorID,
		Action:    action,
		BizType:   bizType,
		BizID:     &bizID,
		Meta:      meta,
		Detail:    detail,
	})
}

func IsNotFound(err error) bool {
	return errors.Is(err, gorm.ErrRecordNotFound)
}
