package pso

import (
	"strings"
	"system-altrak/internal/domain"
	"system-altrak/internal/repository"
	"system-altrak/pkg/utils"
	"time"

	"gorm.io/gorm"
)

type PSORepository interface {
	ListOutstandingRecords(page, limit int, search string, branchID uint, startDate, endDate, aging string) ([]domain.OperationalRecord, int64, error)
	ProcessOutstandingRecordsBatches(search string, branchID uint, batchSize int, process func([]domain.OperationalRecord) error) error
	GetRecordByID(id uint, branchID uint) (*domain.OperationalRecord, error)
	GetRecordByPsoNo(psoNo string) (*domain.OperationalRecord, error)
	GetRecordsByPsoNos(psoNos []string) ([]domain.OperationalRecord, error)
	UpdateRecord(record *domain.OperationalRecord) error // record already contains BranchID
	VerifyRecord(id uint, branchID uint, verifiedBy uint) error
	UpsertRecord(record *domain.OperationalRecord) error
	DeleteRecord(id uint, branchID uint) error
	MarkAsExported(ids []uint, branchID uint, exportedBy uint) error
	WithTransaction(fn func(repo PSORepository) error) error
}

type repositoryImpl struct {
	base *repository.Repository
}

func NewRepository(base *repository.Repository) PSORepository {
	return &repositoryImpl{base: base}
}

func (r *repositoryImpl) ListOutstandingRecords(page, limit int, search string, branchID uint, startDate, endDate, aging string) ([]domain.OperationalRecord, int64, error) {
	var records []domain.OperationalRecord
	var total int64
	query := r.base.GetDB().Model(&domain.OperationalRecord{})
	if branchID > 0 {
		query = query.Where("branch_id = ?", branchID)
	}

	// Filter Search
	if search != "" {
		query = query.Where("pso_no LIKE ? OR po_no LIKE ? OR customer_name LIKE ?", "%"+search+"%", "%"+search+"%", "%"+search+"%")
	}

	// Filter: Rentang Tanggal
	if startDate != "" && endDate != "" {
		query = query.Where("pso_date BETWEEN ? AND ?", startDate, endDate+" 23:59:59")
	} else if startDate != "" {
		query = query.Where("pso_date >= ?", startDate)
	} else if endDate != "" {
		query = query.Where("pso_date <= ?", endDate+" 23:59:59")
	}

	// Filter: Umur Dokumen
	if aging != "" {
		days := utils.ParseInt(aging)
		if days > 0 {
			cutoff := time.Now().AddDate(0, 0, -days)
			query = query.Where("pso_date <= ?", cutoff)
		}
	}

	if limit > 0 {
		query.Count(&total)
	}

	query = query.Order("is_verified asc, customer_name asc, pso_no asc")
	if limit > 0 {
		offset := (page - 1) * limit
		query = query.Limit(limit).Offset(offset)
	}
	err := query.Find(&records).Error
	if err == nil && limit <= 0 {
		total = int64(len(records))
	}
	return records, total, err
}

func (r *repositoryImpl) ProcessOutstandingRecordsBatches(search string, branchID uint, batchSize int, process func([]domain.OperationalRecord) error) error {
	query := r.base.GetDB().Model(&domain.OperationalRecord{})
	if branchID > 0 {
		query = query.Where("branch_id = ?", branchID)
	}
	if search != "" {
		query = query.Where("pso_no LIKE ? OR po_no LIKE ? OR customer_name LIKE ?", "%"+search+"%", "%"+search+"%", "%"+search+"%")
	}
	query = query.Order("customer_name asc, pso_no asc")

	var batchRecords []domain.OperationalRecord

	return query.FindInBatches(&batchRecords, batchSize, func(tx *gorm.DB, batch int) error {
		if len(batchRecords) == 0 {
			return nil
		}

		currentBatch := make([]domain.OperationalRecord, len(batchRecords))
		copy(currentBatch, batchRecords)

		return process(currentBatch)
	}).Error
}

func (r *repositoryImpl) MarkAsExported(ids []uint, branchID uint, exportedBy uint) error {
	now := time.Now()
	query := r.base.GetDB().Model(&domain.OperationalRecord{}).Where("id IN ?", ids)
	if branchID > 0 {
		query = query.Where("branch_id = ?", branchID)
	}
	return query.Updates(map[string]interface{}{
		"exported_at": now,
		"exported_by": exportedBy,
		"updated_at":  now,
	}).Error
}
func (r *repositoryImpl) GetRecordByID(id uint, branchID uint) (*domain.OperationalRecord, error) {
	var record domain.OperationalRecord
	query := r.base.GetDB()
	if branchID > 0 {
		query = query.Where("branch_id = ?", branchID)
	}
	err := query.First(&record, id).Error
	return &record, err
}

func (r *repositoryImpl) GetRecordByPsoNo(psoNo string) (*domain.OperationalRecord, error) {
	var record domain.OperationalRecord
	err := r.base.GetDB().Where("pso_no = ?", strings.TrimSpace(psoNo)).First(&record).Error
	return &record, err
}

func (r *repositoryImpl) UpdateRecord(record *domain.OperationalRecord) error {
	query := r.base.GetDB().Model(&domain.OperationalRecord{}).Where("id = ?", record.ID)
	if record.BranchID > 0 {
		query = query.Where("branch_id = ?", record.BranchID)
	}

	tx := query.Updates(map[string]interface{}{
		"pso_no":        record.PsoNo,
		"pso_date":      record.PsoDate,
		"po_no":         record.PoNo,
		"customer_name": record.CustomerName,
		"amount_idr":    record.AmountIDR,
		"amount":        record.Amount,
		"record_status": record.RecordStatus,
		"is_verified":   record.IsVerified,
		"verified_at":   record.VerifiedAt,
		"verified_by":   record.VerifiedBy,
		"remark":        record.Remark,
		"top":           record.TOP,
		"days":          record.Days,
		"discount":      record.Discount,
		"currency":      record.Currency,
		"updated_at":    time.Now(),
	})
	if tx.Error != nil {
		return tx.Error
	}
	if tx.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}

	return nil
}

func (r *repositoryImpl) VerifyRecord(id uint, branchID uint, verifiedBy uint) error {
	query := r.base.GetDB().Model(&domain.OperationalRecord{}).Where("id = ?", id)
	if branchID > 0 {
		query = query.Where("branch_id = ?", branchID)
	}
	tx := query.Updates(map[string]interface{}{
		"is_verified": true,
		"verified_at": time.Now(),
		"verified_by": verifiedBy,
	})
	if tx.Error != nil {
		return tx.Error
	}
	if tx.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}

	return nil
}

func (r *repositoryImpl) UpsertRecord(record *domain.OperationalRecord) error {
	return r.base.UpsertOperationalRecord(record)
}
func (r *repositoryImpl) DeleteRecord(id uint, branchID uint) error {
	query := r.base.GetDB().Where("id = ?", id)
	if branchID > 0 {
		query = query.Where("branch_id = ?", branchID)
	}
	tx := query.Delete(&domain.OperationalRecord{})
	if tx.Error != nil {
		return tx.Error
	}
	if tx.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}

	return nil
}

func (r *repositoryImpl) GetRecordsByPsoNos(psoNos []string) ([]domain.OperationalRecord, error) {
	var records []domain.OperationalRecord
	err := r.base.GetDB().Where("pso_no IN ?", psoNos).Find(&records).Error
	return records, err
}

func (r *repositoryImpl) WithTransaction(fn func(repo PSORepository) error) error {
	return r.base.GetDB().Transaction(func(tx *gorm.DB) error {
		newBase := repository.NewRepository(tx)
		newRepo := NewRepository(newBase)
		return fn(newRepo)
	})
}
