package repositories

import (
	"gorm.io/gorm"

	"github.com/treytartt/honeydue-api/internal/models"
)

// ContractorRepository handles database operations for contractors
type ContractorRepository struct {
	db *gorm.DB
}

// NewContractorRepository creates a new contractor repository
func NewContractorRepository(db *gorm.DB) *ContractorRepository {
	return &ContractorRepository{db: db}
}

// FindByID finds a contractor by ID with preloaded relations
func (r *ContractorRepository) FindByID(id uint) (*models.Contractor, error) {
	var contractor models.Contractor
	err := r.db.Preload("CreatedBy").
		Preload("Specialties").
		Preload("Tasks").
		Where("id = ? AND is_active = ?", id, true).
		First(&contractor).Error
	if err != nil {
		return nil, err
	}
	return &contractor, nil
}

// FindByResidence finds all contractors for a residence
func (r *ContractorRepository) FindByResidence(residenceID uint) ([]models.Contractor, error) {
	var contractors []models.Contractor
	err := r.db.Preload("CreatedBy").
		Preload("Specialties").
		Where("residence_id = ? AND is_active = ?", residenceID, true).
		Order("is_favorite DESC, name ASC").
		Find(&contractors).Error
	return contractors, err
}

// FindByUser finds all contractors accessible to a user
// Returns contractors that either:
// 1. Have no residence (personal contractors) AND were created by the user
// 2. Belong to a residence the user has access to
func (r *ContractorRepository) FindByUser(userID uint, residenceIDs []uint) ([]models.Contractor, error) {
	var contractors []models.Contractor
	query := r.db.Preload("CreatedBy").
		Preload("Specialties").
		Preload("Residence").
		Where("is_active = ?", true)

	if len(residenceIDs) > 0 {
		// Personal contractors (no residence, created by user) OR residence contractors
		query = query.Where(
			"(residence_id IS NULL AND created_by_id = ?) OR (residence_id IN ?)",
			userID, residenceIDs,
		)
	} else {
		// Only personal contractors
		query = query.Where("residence_id IS NULL AND created_by_id = ?", userID)
	}

	err := query.Order("is_favorite DESC, name ASC").Limit(500).Find(&contractors).Error
	return contractors, err
}

// Create creates a new contractor
func (r *ContractorRepository) Create(contractor *models.Contractor) error {
	return r.db.Create(contractor).Error
}

// Update updates a contractor
// Uses Omit to exclude associations that could interfere with Save
func (r *ContractorRepository) Update(contractor *models.Contractor) error {
	return r.db.Omit("CreatedBy", "Specialties", "Tasks", "Residence").Save(contractor).Error
}

// Delete soft-deletes a contractor
func (r *ContractorRepository) Delete(id uint) error {
	return r.db.Model(&models.Contractor{}).
		Where("id = ?", id).
		Update("is_active", false).Error
}

// ToggleFavorite toggles the favorite status of a contractor atomically.
// Uses a single UPDATE with NOT to avoid read-then-write race conditions.
// Only toggles active contractors to prevent toggling soft-deleted records.
func (r *ContractorRepository) ToggleFavorite(id uint) (bool, error) {
	var newStatus bool
	err := r.db.Transaction(func(tx *gorm.DB) error {
		// Atomic toggle: SET is_favorite = NOT is_favorite for active contractors only
		result := tx.Model(&models.Contractor{}).
			Where("id = ? AND is_active = ?", id, true).
			Update("is_favorite", gorm.Expr("NOT is_favorite"))
		if result.Error != nil {
			return result.Error
		}
		if result.RowsAffected == 0 {
			return gorm.ErrRecordNotFound
		}

		// Read back the new value within the same transaction
		var contractor models.Contractor
		if err := tx.Select("is_favorite").First(&contractor, id).Error; err != nil {
			return err
		}
		newStatus = contractor.IsFavorite
		return nil
	})
	return newStatus, err
}

// GetTasksForContractor gets all tasks associated with a contractor
func (r *ContractorRepository) GetTasksForContractor(contractorID uint) ([]models.Task, error) {
	var tasks []models.Task
	err := r.db.Preload("Category").
		Preload("Priority").
		Where("contractor_id = ?", contractorID).
		Order("due_date ASC NULLS LAST").
		Find(&tasks).Error
	return tasks, err
}

// SetSpecialties sets the specialties for a contractor.
// Wrapped in a transaction so that clearing existing specialties and
// appending new ones are atomic -- a failure in either step rolls back both.
func (r *ContractorRepository) SetSpecialties(contractorID uint, specialtyIDs []uint) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		var contractor models.Contractor
		if err := tx.First(&contractor, contractorID).Error; err != nil {
			return err
		}

		// Clear existing specialties
		if err := tx.Model(&contractor).Association("Specialties").Clear(); err != nil {
			return err
		}

		if len(specialtyIDs) == 0 {
			return nil
		}

		// Add new specialties
		var specialties []models.ContractorSpecialty
		if err := tx.Where("id IN ?", specialtyIDs).Find(&specialties).Error; err != nil {
			return err
		}

		return tx.Model(&contractor).Association("Specialties").Append(specialties)
	})
}

// CountByResidence counts contractors in a residence
func (r *ContractorRepository) CountByResidence(residenceID uint) (int64, error) {
	var count int64
	err := r.db.Model(&models.Contractor{}).
		Where("residence_id = ? AND is_active = ?", residenceID, true).
		Count(&count).Error
	return count, err
}

// CountByResidenceIDs counts all active contractors across multiple residences in a single query.
// Returns the total count of active contractors for the given residence IDs.
func (r *ContractorRepository) CountByResidenceIDs(residenceIDs []uint) (int64, error) {
	if len(residenceIDs) == 0 {
		return 0, nil
	}
	var count int64
	err := r.db.Model(&models.Contractor{}).
		Where("residence_id IN ? AND is_active = ?", residenceIDs, true).
		Count(&count).Error
	return count, err
}

// === Specialty Operations ===

// GetAllSpecialties returns all contractor specialties
func (r *ContractorRepository) GetAllSpecialties() ([]models.ContractorSpecialty, error) {
	var specialties []models.ContractorSpecialty
	err := r.db.Order("display_order, name").Find(&specialties).Error
	return specialties, err
}

// FindSpecialtyByID finds a specialty by ID
func (r *ContractorRepository) FindSpecialtyByID(id uint) (*models.ContractorSpecialty, error) {
	var specialty models.ContractorSpecialty
	err := r.db.First(&specialty, id).Error
	if err != nil {
		return nil, err
	}
	return &specialty, nil
}
