package repository

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

	"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) GetDB() *gorm.DB {
	if ctx := utils.GetContext(); ctx != nil {
		return r.db.WithContext(ctx)
	}
	return r.db
}

func (r *Repository) WithTransaction(fn func(repo *Repository) error) error {
	return r.GetDB().Transaction(func(tx *gorm.DB) error {
		return fn(&Repository{db: tx})
	})
}

// Operational Records (PSO/PO Lifecycle Node)
func (r *Repository) CreateOperationalRecord(record *domain.OperationalRecord) error {
	return r.GetDB().Create(record).Error
}

func (r *Repository) GetOperationalRecordByID(id uint, branchID uint, role string) (*domain.OperationalRecord, error) {
	var record domain.OperationalRecord
	q := r.GetDB()
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.First(&record, id).Error
	return &record, err
}

func (r *Repository) UpdateOperationalRecord(record *domain.OperationalRecord) error {
	return r.GetDB().Save(record).Error
}

func (r *Repository) GetOperationalRecordByPsoNo(psoIdentifier string) (*domain.OperationalRecord, error) {
	var record domain.OperationalRecord
	err := r.GetDB().Where("pso_no = ?", psoIdentifier).First(&record).Error
	return &record, err
}

func (r *Repository) UpsertOperationalRecord(record *domain.OperationalRecord) error {
	record.PsoNo = strings.TrimSpace(record.PsoNo)
	if record.PsoNo == "" {
		return errors.New("empty PSO identifier")
	}

	return r.GetDB().Clauses(clause.OnConflict{
		Columns: []clause.Column{{Name: "pso_no"}},
		DoUpdates: clause.AssignmentColumns([]string{
			"pso_date", "po_no", "customer_name", "amount_idr", "amount",
			"remark", "top", "currency", "days", "discount", "updated_at",
			"record_status",
		}),
	}).Unscoped().Create(record).Error
}

func (r *Repository) ListOperationalRecords(status string, page, limit int, branchID uint, role string) ([]domain.OperationalRecord, int64, error) {
	var records []domain.OperationalRecord
	var total int64

	query := r.GetDB().Model(&domain.OperationalRecord{})
	if status != "" {
		query = query.Where("record_status = ?", status)
	}
	if role != "superadmin" && branchID > 0 {
		query = query.Where("branch_id = ?", branchID)
	}

	query.Count(&total)
	offset := (page - 1) * limit
	err := query.Limit(limit).Offset(offset).Order("created_at desc").Find(&records).Error
	return records, total, err
}

// Service Requisition
func (r *Repository) GetServiceRequisitionByID(id uint, branchID uint, role string) (*domain.ServiceRequisition, error) {
	var sr domain.ServiceRequisition
	q := r.GetDB().Preload("Checklist").Preload("Equipments")
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.First(&sr, id).Error
	return &sr, err
}

func (r *Repository) ListServiceRequisitions(branchID uint, role string) ([]domain.ServiceRequisition, error) {
	var srs []domain.ServiceRequisition
	q := r.GetDB().Order("created_at desc")
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.Find(&srs).Error
	return srs, err
}

func (r *Repository) CreateServiceRequisition(sr *domain.ServiceRequisition) error {
	return r.GetDB().Transaction(func(tx *gorm.DB) error {
		// Save the main record first, omitting associations to handle them manually.
		if err := tx.Omit("Checklist", "Equipments").Create(sr).Error; err != nil {
			return err
		}

		// Save the checklist manually, ensuring ID is 0 for auto-increment.
		if sr.Checklist != nil {
			sr.Checklist.ID = 0
			sr.Checklist.ServiceRequisitionID = sr.ID
			if err := tx.Omit("ID").Create(sr.Checklist).Error; err != nil {
				return err
			}
		}

		// Save each equipment manually, ensuring ID is 0 for auto-increment.
		for i := range sr.Equipments {
			sr.Equipments[i].ID = 0
			sr.Equipments[i].ServiceRequisitionID = sr.ID
			if err := tx.Omit("ID").Create(&sr.Equipments[i]).Error; err != nil {
				return err
			}
		}

		return nil
	})
}

// Memorandums
func (r *Repository) CreateMemorandum(memo *domain.Memorandum) error {
	return r.GetDB().Create(memo).Error
}

func (r *Repository) GetMemorandumByID(id uint, branchID uint, role string) (*domain.Memorandum, error) {
	var memo domain.Memorandum
	q := r.GetDB()
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.First(&memo, id).Error
	return &memo, err
}

func (r *Repository) ListMemorandums(branchID uint, role string) ([]domain.Memorandum, error) {
	var memos []domain.Memorandum
	q := r.GetDB().Order("created_at desc")
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.Find(&memos).Error
	return memos, err
}

// Serial Sequence Node
func (r *Repository) GetNextSequence(docType string, year, month int) (int, error) {
	var seq domain.SerialSequence
	err := r.GetDB().Transaction(func(tx *gorm.DB) error {
		err := tx.
			Where(domain.SerialSequence{DocType: docType, Year: year, Month: month}).
			FirstOrCreate(&seq, domain.SerialSequence{DocType: docType, Year: year, Month: month}).Error
		if err != nil {
			return err
		}

		seq.CurrentNumber++
		return tx.Save(&seq).Error
	})
	return seq.CurrentNumber, err
}

// Credit Limit
func (r *Repository) CreateCreditLimit(cl *domain.CreditLimit) error {
	return r.GetDB().Create(cl).Error
}

func (r *Repository) GetCreditLimitByID(id uint, branchID uint, role string) (*domain.CreditLimit, error) {
	var cl domain.CreditLimit
	q := r.GetDB()
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.First(&cl, id).Error
	return &cl, err
}

func (r *Repository) ListCreditLimits(branchID uint, role string) ([]domain.CreditLimit, error) {
	var cls []domain.CreditLimit
	q := r.GetDB().Order("created_at desc")
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.Find(&cls).Error
	return cls, err
}

// Service Authorization Memorandum Node
func (r *Repository) CreateServiceAuthorization(auth *domain.ServiceAuthorization) error {
	return r.GetDB().Create(auth).Error
}

func (r *Repository) GetServiceAuthorizationByID(id uint, branchID uint, role string) (*domain.ServiceAuthorization, error) {
	var auth domain.ServiceAuthorization
	q := r.GetDB()
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.First(&auth, id).Error
	return &auth, err
}

func (r *Repository) ListServiceAuthorizations(branchID uint, role string) ([]domain.ServiceAuthorization, error) {
	var list []domain.ServiceAuthorization
	q := r.GetDB().Order("created_at desc")
	if role != "superadmin" && branchID > 0 {
		q = q.Where("branch_id = ?", branchID)
	}
	err := q.Find(&list).Error
	return list, err
}
