package repository

import (
	"context"
	"time"

	"eslogad-be/internal/entities"

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

type LetterOutgoingRepository struct{ db *gorm.DB }

func NewLetterOutgoingRepository(db *gorm.DB) *LetterOutgoingRepository {
	return &LetterOutgoingRepository{db: db}
}

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

func (r *LetterOutgoingRepository) Get(ctx context.Context, id uuid.UUID) (*entities.LetterOutgoing, error) {
	db := DBFromContext(ctx, r.db)
	var e entities.LetterOutgoing
	if err := db.WithContext(ctx).
		Preload("Priority").
		Preload("ReceiverInstitution").
		Preload("Creator").
		Preload("ApprovalFlow").
		Preload("Recipients").
		Preload("Attachments").
		Preload("Approvals.Step").
		Preload("Approvals.Approver").
		Where("id = ? AND deleted_at IS NULL", id).
		First(&e).Error; err != nil {
		return nil, err
	}
	return &e, nil
}

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

func (r *LetterOutgoingRepository) SoftDelete(ctx context.Context, id uuid.UUID) error {
	db := DBFromContext(ctx, r.db)
	now := time.Now()
	return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", id).Update("deleted_at", now).Error
}

func (r *LetterOutgoingRepository) BulkArchive(ctx context.Context, letterIDs []uuid.UUID) (int64, error) {
	db := DBFromContext(ctx, r.db)
	result := db.WithContext(ctx).
		Model(&entities.LetterOutgoing{}).
		Where("id IN ? AND deleted_at IS NULL", letterIDs).
		Update("status", "archived")
	return result.RowsAffected, result.Error
}

func (r *LetterOutgoingRepository) GetWithRelations(ctx context.Context, id uuid.UUID, relations []string) (*entities.LetterOutgoing, error) {
	db := DBFromContext(ctx, r.db)
	query := db.WithContext(ctx).Where("id = ? AND deleted_at IS NULL", id)

	// Preload all specified relations
	for _, relation := range relations {
		query = query.Preload(relation)
	}

	var e entities.LetterOutgoing
	if err := query.First(&e).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, gorm.ErrRecordNotFound
		}
		return nil, err
	}

	return &e, nil
}

type ListOutgoingLettersFilter struct {
	Status                *string
	Query                 *string
	CreatedBy             *uuid.UUID
	DepartmentID          *uuid.UUID
	UserID                *uuid.UUID
	ReceiverInstitutionID *uuid.UUID
	FromDate              *time.Time
	ToDate                *time.Time
	PriorityID            *uuid.UUID
	SortBy                *string
	SortOrder             *string
	IsArchived            *bool
}

func (r *LetterOutgoingRepository) List(ctx context.Context, filter ListOutgoingLettersFilter, limit, offset int) ([]entities.LetterOutgoing, int64, error) {
	db := DBFromContext(ctx, r.db)
	query := db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("deleted_at IS NULL")

	// Apply is_archived filter
	if filter.IsArchived != nil {
		if *filter.IsArchived {
			query = query.Where("letters_outgoing.status = 'archived'")
		} else {
			query = query.Where("letters_outgoing.status != 'archived'")
		}
	}

	if filter.Status != nil {
		query = query.Where("letters_outgoing.status = ?", *filter.Status)
	}
	if filter.Query != nil {
		q := "%" + *filter.Query + "%"
		query = query.Where("subject ILIKE ? OR reference_number ILIKE ? OR letter_number ILIKE ?", q, q, q)
	}
	if filter.CreatedBy != nil {
		query = query.Where("created_by = ?", *filter.CreatedBy)
	}
	// Filter by UserID through recipients
	if filter.UserID != nil {
		query = query.Joins("LEFT JOIN letter_outgoing_recipients ON letter_outgoing_recipients.letter_id = letters_outgoing.id").
			Where("letter_outgoing_recipients.user_id = ?", *filter.UserID).
			Distinct()
	}
	if filter.ReceiverInstitutionID != nil {
		query = query.Where("receiver_institution_id = ?", *filter.ReceiverInstitutionID)
	}
	if filter.PriorityID != nil {
		query = query.Where("priority_id = ?", *filter.PriorityID)
	}
	if filter.FromDate != nil {
		query = query.Where("issue_date >= ?", *filter.FromDate)
	}
	if filter.ToDate != nil {
		query = query.Where("issue_date <= ?", *filter.ToDate)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	orderBy := "created_at DESC" // default
	if filter.SortBy != nil {
		sortField := *filter.SortBy
		sortDirection := "ASC"
		if filter.SortOrder != nil && (*filter.SortOrder == "desc" || *filter.SortOrder == "DESC") {
			sortDirection = "DESC"
		}

		switch sortField {
		case "letter_number":
			orderBy = "letter_number " + sortDirection
		case "subject":
			orderBy = "subject " + sortDirection
		case "issue_date":
			orderBy = "issue_date " + sortDirection
		case "status":
			orderBy = "status " + sortDirection
		case "created_at":
			orderBy = "created_at " + sortDirection
		default:
			orderBy = "created_at " + sortDirection
		}
	}

	var list []entities.LetterOutgoing
	if err := query.
		Preload("Priority").
		Preload("ReceiverInstitution").
		Preload("Creator").
		Preload("Creator.Profile").
		Preload("Creator.Departments").
		Preload("Recipients").
		Preload("Recipients.User").
		Preload("Recipients.Department").
		Preload("Attachments").
		Preload("Approvals.Step").
		Preload("Approvals.Approver").
		Order(orderBy).
		Limit(limit).
		Offset(offset).
		Find(&list).Error; err != nil {
		return nil, 0, err
	}
	return list, total, nil
}

func (r *LetterOutgoingRepository) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.LetterOutgoingStatus) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Model(&entities.LetterOutgoing{}).Where("id = ? AND deleted_at IS NULL", id).Update("status", status).Error
}

type LetterOutgoingAttachmentRepository struct{ db *gorm.DB }

func NewLetterOutgoingAttachmentRepository(db *gorm.DB) *LetterOutgoingAttachmentRepository {
	return &LetterOutgoingAttachmentRepository{db: db}
}

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

func (r *LetterOutgoingAttachmentRepository) CreateBulk(ctx context.Context, list []entities.LetterOutgoingAttachment) error {
	db := DBFromContext(ctx, r.db)
	if len(list) == 0 {
		return nil
	}
	return db.WithContext(ctx).Create(&list).Error
}

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

func (r *LetterOutgoingAttachmentRepository) Delete(ctx context.Context, id uuid.UUID) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Where("id = ?", id).Delete(&entities.LetterOutgoingAttachment{}).Error
}

// ListByLetterIDs fetches attachments for multiple letters in a single query
func (r *LetterOutgoingAttachmentRepository) ListByLetterIDs(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingAttachment, error) {
	if len(letterIDs) == 0 {
		return make(map[uuid.UUID][]entities.LetterOutgoingAttachment), nil
	}
	
	db := DBFromContext(ctx, r.db)
	var attachments []entities.LetterOutgoingAttachment
	if err := db.WithContext(ctx).Where("letter_id IN ?", letterIDs).Order("uploaded_at ASC").Find(&attachments).Error; err != nil {
		return nil, err
	}
	
	// Group attachments by letter ID
	result := make(map[uuid.UUID][]entities.LetterOutgoingAttachment)
	for _, att := range attachments {
		result[att.LetterID] = append(result[att.LetterID], att)
	}
	
	return result, nil
}

type LetterOutgoingRecipientRepository struct{ db *gorm.DB }

func NewLetterOutgoingRecipientRepository(db *gorm.DB) *LetterOutgoingRecipientRepository {
	return &LetterOutgoingRecipientRepository{db: db}
}

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

func (r *LetterOutgoingRecipientRepository) CreateBulk(ctx context.Context, list []entities.LetterOutgoingRecipient) error {
	db := DBFromContext(ctx, r.db)
	if len(list) == 0 {
		return nil
	}
	return db.WithContext(ctx).Create(&list).Error
}

func (r *LetterOutgoingRecipientRepository) CountUnreadByUser(ctx context.Context, userID uuid.UUID) (int, error) {
	db := DBFromContext(ctx, r.db)
	var count int64
	if err := db.WithContext(ctx).
		Model(&entities.LetterOutgoingRecipient{}).
		Where("user_id = ? AND read_at IS NULL", userID).
		Count(&count).Error; err != nil {
		return 0, err
	}
	return int(count), nil
}

func (r *LetterOutgoingRecipientRepository) MarkAsRead(ctx context.Context, letterID, userID uuid.UUID) error {
	db := DBFromContext(ctx, r.db)
	now := time.Now()
	return db.WithContext(ctx).
		Model(&entities.LetterOutgoingRecipient{}).
		Where("letter_id = ? AND user_id = ?", letterID, userID).
		Update("read_at", now).Error
}

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

func (r *LetterOutgoingRecipientRepository) Update(ctx context.Context, e *entities.LetterOutgoingRecipient) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Model(&entities.LetterOutgoingRecipient{}).Where("id = ?", e.ID).Updates(e).Error
}

func (r *LetterOutgoingRecipientRepository) Delete(ctx context.Context, id uuid.UUID) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Where("id = ?", id).Delete(&entities.LetterOutgoingRecipient{}).Error
}

func (r *LetterOutgoingRecipientRepository) DeleteByLetter(ctx context.Context, letterID uuid.UUID) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Where("letter_id = ?", letterID).Delete(&entities.LetterOutgoingRecipient{}).Error
}

// ListByLetterIDs fetches recipients for multiple letters in a single query
func (r *LetterOutgoingRecipientRepository) ListByLetterIDs(ctx context.Context, letterIDs []uuid.UUID) (map[uuid.UUID][]entities.LetterOutgoingRecipient, error) {
	if len(letterIDs) == 0 {
		return make(map[uuid.UUID][]entities.LetterOutgoingRecipient), nil
	}
	
	db := DBFromContext(ctx, r.db)
	var recipients []entities.LetterOutgoingRecipient
	if err := db.WithContext(ctx).
		Preload("User").
		Preload("User.Profile").
		Preload("Department").
		Where("letter_id IN ?", letterIDs).
		Order("is_primary DESC, created_at ASC").
		Find(&recipients).Error; err != nil {
		return nil, err
	}
	
	// Group recipients by letter ID
	result := make(map[uuid.UUID][]entities.LetterOutgoingRecipient)
	for _, rec := range recipients {
		result[rec.LetterID] = append(result[rec.LetterID], rec)
	}
	
	return result, nil
}

type LetterOutgoingDiscussionRepository struct{ db *gorm.DB }

func NewLetterOutgoingDiscussionRepository(db *gorm.DB) *LetterOutgoingDiscussionRepository {
	return &LetterOutgoingDiscussionRepository{db: db}
}

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

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

func (r *LetterOutgoingDiscussionRepository) ListByLetter(ctx context.Context, letterID uuid.UUID) ([]entities.LetterOutgoingDiscussion, error) {
	db := DBFromContext(ctx, r.db)
	var list []entities.LetterOutgoingDiscussion
	if err := db.WithContext(ctx).
		Preload("User").
		Preload("Attachments").
		Preload("Replies.User").
		Where("letter_id = ? AND parent_id IS NULL", letterID).
		Order("created_at DESC").
		Find(&list).Error; err != nil {
		return nil, err
	}
	return list, nil
}

func (r *LetterOutgoingDiscussionRepository) Update(ctx context.Context, e *entities.LetterOutgoingDiscussion) error {
	db := DBFromContext(ctx, r.db)
	now := time.Now()
	e.EditedAt = &now
	return db.WithContext(ctx).Model(&entities.LetterOutgoingDiscussion{}).Where("id = ?", e.ID).Updates(e).Error
}

func (r *LetterOutgoingDiscussionRepository) Delete(ctx context.Context, id uuid.UUID) error {
	db := DBFromContext(ctx, r.db)
	return db.WithContext(ctx).Where("id = ?", id).Delete(&entities.LetterOutgoingDiscussion{}).Error
}

type LetterOutgoingDiscussionAttachmentRepository struct{ db *gorm.DB }

func NewLetterOutgoingDiscussionAttachmentRepository(db *gorm.DB) *LetterOutgoingDiscussionAttachmentRepository {
	return &LetterOutgoingDiscussionAttachmentRepository{db: db}
}

func (r *LetterOutgoingDiscussionAttachmentRepository) CreateBulk(ctx context.Context, list []entities.LetterOutgoingDiscussionAttachment) error {
	db := DBFromContext(ctx, r.db)
	if len(list) == 0 {
		return nil
	}
	return db.WithContext(ctx).Create(&list).Error
}

type LetterOutgoingActivityLogRepository struct{ db *gorm.DB }

func NewLetterOutgoingActivityLogRepository(db *gorm.DB) *LetterOutgoingActivityLogRepository {
	return &LetterOutgoingActivityLogRepository{db: db}
}

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

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