package repository

import (
	"context"
	"time"

	"eslogad-be/internal/entities"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

type LetterIncomingRepository struct{ db *gorm.DB }

func NewLetterIncomingRepository(db *gorm.DB) *LetterIncomingRepository {
	return &LetterIncomingRepository{db: db}
}

func (r *LetterIncomingRepository) Create(ctx context.Context, e *entities.LetterIncoming) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Create(e).Error
}
func (r *LetterIncomingRepository) Get(ctx context.Context, id uuid.UUID) (*entities.LetterIncoming, error) {
	db := DBFromContext(ctx, r.db)
	var e entities.LetterIncoming
	if err := db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
		return nil, err
	}
	return &e, nil
}

func (r *LetterIncomingRepository) Update(ctx context.Context, e *entities.LetterIncoming) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Model(&entities.LetterIncoming{}).Where("id = ? AND deleted_at IS NULL", e.ID).Updates(e).Error
}

func (r *LetterIncomingRepository) SoftDelete(ctx context.Context, id uuid.UUID) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Exec("UPDATE letters_incoming SET deleted_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL", id).Error
}

type ListIncomingLettersFilter struct {
	Status *string
	Query  *string
}

func (r *LetterIncomingRepository) List(ctx context.Context, filter ListIncomingLettersFilter, limit, offset int) ([]entities.LetterIncoming, int64, error) {
	db := DBFromContext(ctx, r.db)
	query := db.WithContext(ctx).Model(&entities.LetterIncoming{}).Where("deleted_at IS NULL")
	if filter.Status != nil {
		query = query.Where("status = ?", *filter.Status)
	}
	if filter.Query != nil {
		q := "%" + *filter.Query + "%"
		query = query.Where("subject ILIKE ? OR reference_number ILIKE ?", q, q)
	}
	var total int64
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	var list []entities.LetterIncoming
	if err := query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&list).Error; err != nil {
		return nil, 0, err
	}
	return list, total, nil
}

type LetterIncomingAttachmentRepository struct{ db *gorm.DB }

func NewLetterIncomingAttachmentRepository(db *gorm.DB) *LetterIncomingAttachmentRepository {
	return &LetterIncomingAttachmentRepository{db: db}
}

func (r *LetterIncomingAttachmentRepository) CreateBulk(ctx context.Context, list []entities.LetterIncomingAttachment) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Create(&list).Error
}
func (r *LetterIncomingAttachmentRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingAttachment, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.LetterIncomingAttachment
	if err := db.WithContext(ctx).Where("letter_id = ?", letterID).Order("uploaded_at ASC").Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

type LetterIncomingActivityLogRepository struct{ db *gorm.DB }

func NewLetterIncomingActivityLogRepository(db *gorm.DB) *LetterIncomingActivityLogRepository {
	return &LetterIncomingActivityLogRepository{db: db}
}

func (r *LetterIncomingActivityLogRepository) Create(ctx context.Context, e *entities.LetterIncomingActivityLog) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Create(e).Error
}

func (r *LetterIncomingActivityLogRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingActivityLog, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.LetterIncomingActivityLog
	if err := db.WithContext(ctx).Where("letter_id = ?", letterID).Order("occurred_at ASC").Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

type LetterIncomingDispositionRepository struct{ db *gorm.DB }

func NewLetterIncomingDispositionRepository(db *gorm.DB) *LetterIncomingDispositionRepository {
	return &LetterIncomingDispositionRepository{db: db}
}
func (r *LetterIncomingDispositionRepository) Create(ctx context.Context, e *entities.LetterIncomingDisposition) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Create(e).Error
}
func (r *LetterIncomingDispositionRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterIncomingDisposition, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.LetterIncomingDisposition
	if err := db.WithContext(ctx).
		Where("letter_id = ?", letterID).
		Preload("Department").
		Preload("Departments.Department").
		Preload("ActionSelections.Action").
		Preload("DispositionNotes.User").
		Order("created_at ASC").
		Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

type LetterIncomingDispositionDepartmentRepository struct{ db *gorm.DB }

func NewLetterIncomingDispositionDepartmentRepository(db *gorm.DB) *LetterIncomingDispositionDepartmentRepository {
	return &LetterIncomingDispositionDepartmentRepository{db: db}
}
func (r *LetterIncomingDispositionDepartmentRepository) CreateBulk(ctx context.Context, list []entities.LetterIncomingDispositionDepartment) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Create(&list).Error
}
func (r *LetterIncomingDispositionDepartmentRepository) ListByDisposition(ctx context.Context, dispositionID uuid.UUID) ([]entities.LetterIncomingDispositionDepartment, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.LetterIncomingDispositionDepartment
	if err := db.WithContext(ctx).Where("letter_incoming_disposition_id = ?", dispositionID).Order("created_at ASC").Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

func (r *LetterIncomingDispositionDepartmentRepository) ListByDispositions(ctx context.Context, dispositionIDs []uuid.UUID) ([]entities.LetterIncomingDispositionDepartment, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.LetterIncomingDispositionDepartment
	if len(dispositionIDs) == 0 {
		return list, nil
	}
	if err := db.WithContext(ctx).Where("letter_incoming_disposition_id IN ?", dispositionIDs).Order("letter_incoming_disposition_id, created_at ASC").Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

type DispositionNoteRepository struct{ db *gorm.DB }

func NewDispositionNoteRepository(db *gorm.DB) *DispositionNoteRepository {
	return &DispositionNoteRepository{db: db}
}
func (r *DispositionNoteRepository) Create(ctx context.Context, e *entities.DispositionNote) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Create(e).Error
}

func (r *DispositionNoteRepository) ListByDisposition(ctx context.Context, dispositionID uuid.UUID) ([]entities.DispositionNote, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.DispositionNote
	if err := db.WithContext(ctx).Where("disposition_id = ?", dispositionID).Order("created_at ASC").Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

func (r *DispositionNoteRepository) ListByDispositions(ctx context.Context, dispositionIDs []uuid.UUID) ([]entities.DispositionNote, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.DispositionNote
	if len(dispositionIDs) == 0 {
		return list, nil
	}
	if err := db.WithContext(ctx).Where("disposition_id IN ?", dispositionIDs).Order("disposition_id, created_at ASC").Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

type LetterDispositionActionSelectionRepository struct{ db *gorm.DB }

func NewLetterDispositionActionSelectionRepository(db *gorm.DB) *LetterDispositionActionSelectionRepository {
	return &LetterDispositionActionSelectionRepository{db: db}
}
func (r *LetterDispositionActionSelectionRepository) CreateBulk(ctx context.Context, list []entities.LetterDispositionActionSelection) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Create(&list).Error
}
func (r *LetterDispositionActionSelectionRepository) ListByDisposition(ctx context.Context, dispositionID uuid.UUID) ([]entities.LetterDispositionActionSelection, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.LetterDispositionActionSelection
	if err := db.WithContext(ctx).Where("disposition_id = ?", dispositionID).Order("created_at ASC").Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

func (r *LetterDispositionActionSelectionRepository) ListByDispositions(ctx context.Context, dispositionIDs []uuid.UUID) ([]entities.LetterDispositionActionSelection, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.LetterDispositionActionSelection
	if len(dispositionIDs) == 0 {
		return list, nil
	}
	if err := db.WithContext(ctx).Where("disposition_id IN ?", dispositionIDs).Order("disposition_id, created_at ASC").Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

type LetterDiscussionRepository struct{ db *gorm.DB }

func NewLetterDiscussionRepository(db *gorm.DB) *LetterDiscussionRepository {
	return &LetterDiscussionRepository{db: db}
}
func (r *LetterDiscussionRepository) Create(ctx context.Context, e *entities.LetterDiscussion) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Create(e).Error
}
func (r *LetterDiscussionRepository) Get(ctx context.Context, id uuid.UUID) (*entities.LetterDiscussion, error) {
	db := DBFromContext(ctx, r.db)
	var e entities.LetterDiscussion
	if err := db.WithContext(ctx).First(&e, "id = ?", id).Error; err != nil {
		return nil, err
	}
	return &e, nil
}
func (r *LetterDiscussionRepository) Update(ctx context.Context, e *entities.LetterDiscussion) error {
	db := DBFromContext(ctx, r.db)
	// ensure edited_at is set when updating
	if e.EditedAt == nil {
		now := time.Now()
		e.EditedAt = &now
	}
	return db.WithContext(ctx).Model(&entities.LetterDiscussion{}).
		Where("id = ?", e.ID).
		Updates(map[string]interface{}{"message": e.Message, "mentions": e.Mentions, "edited_at": e.EditedAt}).Error
}

func (r *LetterDiscussionRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterDiscussion, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.LetterDiscussion
	if err := db.WithContext(ctx).
		Where("letter_id = ?", letterID).
		Preload("User.Profile").
		Order("created_at ASC").
		Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

func (r *LetterDiscussionRepository) GetUsersByIDs(ctx context.Context, userIDs []uuid.UUID) ([]entities.User, error) {
	if len(userIDs) == 0 {
		return []entities.User{}, nil
	}

	db := DBFromContext(ctx, r.db)
	var users []entities.User
	if err := db.WithContext(ctx).
		Where("id IN ?", userIDs).
		Preload("Profile").
		Find(&users).Error; err != nil {
		return nil, err
	}
	return users, nil
}

type AppSettingRepository struct{ db *gorm.DB }

func NewAppSettingRepository(db *gorm.DB) *AppSettingRepository { return &AppSettingRepository{db: db} }
func (r *AppSettingRepository) Get(ctx context.Context, key string) (*entities.AppSetting, error) {
	db := DBFromContext(ctx, r.db)
	var e entities.AppSetting
	if err := db.WithContext(ctx).First(&e, "key = ?", key).Error; err != nil {
		return nil, err
	}
	return &e, nil
}
func (r *AppSettingRepository) Upsert(ctx context.Context, key string, value entities.JSONB) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Exec("INSERT INTO app_settings(key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value = EXCLUDED.value, updated_at = CURRENT_TIMESTAMP", key, value).Error
}

// recipients

type LetterIncomingRecipientRepository struct{ db *gorm.DB }

func NewLetterIncomingRecipientRepository(db *gorm.DB) *LetterIncomingRecipientRepository {
	return &LetterIncomingRecipientRepository{db: db}
}
func (r *LetterIncomingRecipientRepository) CreateBulk(ctx context.Context, recs []entities.LetterIncomingRecipient) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Create(&recs).Error
}
