package services

import (
	"errors"

	"gorm.io/gorm"

	"github.com/treytartt/honeydue-api/internal/apperrors"
	"github.com/treytartt/honeydue-api/internal/dto/requests"
	"github.com/treytartt/honeydue-api/internal/dto/responses"
	"github.com/treytartt/honeydue-api/internal/models"
	"github.com/treytartt/honeydue-api/internal/repositories"
)

// Deprecated: Use apperrors.NotFound("error.contractor_not_found") instead
// var (
// 	ErrContractorNotFound     = errors.New("contractor not found")
// 	ErrContractorAccessDenied = errors.New("you do not have access to this contractor")
// )

// ContractorService handles contractor business logic
type ContractorService struct {
	contractorRepo *repositories.ContractorRepository
	residenceRepo  *repositories.ResidenceRepository
}

// NewContractorService creates a new contractor service
func NewContractorService(contractorRepo *repositories.ContractorRepository, residenceRepo *repositories.ResidenceRepository) *ContractorService {
	return &ContractorService{
		contractorRepo: contractorRepo,
		residenceRepo:  residenceRepo,
	}
}

// GetContractor gets a contractor by ID with access check
func (s *ContractorService) GetContractor(contractorID, userID uint) (*responses.ContractorResponse, error) {
	contractor, err := s.contractorRepo.FindByID(contractorID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, apperrors.NotFound("error.contractor_not_found")
		}
		return nil, apperrors.Internal(err)
	}

	// Check access
	if !s.hasContractorAccess(contractor, userID) {
		return nil, apperrors.Forbidden("error.contractor_access_denied")
	}

	resp := responses.NewContractorResponse(contractor)
	return &resp, nil
}

// hasContractorAccess checks if user has access to a contractor
// Access rules:
// - If contractor has no residence: only the creator has access
// - If contractor has a residence: all users with access to that residence
func (s *ContractorService) hasContractorAccess(contractor *models.Contractor, userID uint) bool {
	if contractor.ResidenceID == nil {
		// Personal contractor - only creator has access
		return contractor.CreatedByID == userID
	}

	// Residence contractor - check residence access
	hasAccess, err := s.residenceRepo.HasAccess(*contractor.ResidenceID, userID)
	if err != nil {
		return false
	}
	return hasAccess
}

// ListContractors lists all contractors accessible to a user
func (s *ContractorService) ListContractors(userID uint) ([]responses.ContractorResponse, error) {
	// Get residence IDs (lightweight - no preloads)
	residenceIDs, err := s.residenceRepo.FindResidenceIDsByUser(userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// FindByUser now handles both personal and residence contractors
	contractors, err := s.contractorRepo.FindByUser(userID, residenceIDs)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	return responses.NewContractorListResponse(contractors), nil
}

// CreateContractor creates a new contractor
func (s *ContractorService) CreateContractor(req *requests.CreateContractorRequest, userID uint) (*responses.ContractorResponse, error) {
	// If residence is provided, check access
	if req.ResidenceID != nil {
		hasAccess, err := s.residenceRepo.HasAccess(*req.ResidenceID, userID)
		if err != nil {
			return nil, apperrors.Internal(err)
		}
		if !hasAccess {
			return nil, apperrors.Forbidden("error.residence_access_denied")
		}
	}

	isFavorite := false
	if req.IsFavorite != nil {
		isFavorite = *req.IsFavorite
	}

	contractor := &models.Contractor{
		ResidenceID:   req.ResidenceID,
		CreatedByID:   userID,
		Name:          req.Name,
		Company:       req.Company,
		Phone:         req.Phone,
		Email:         req.Email,
		Website:       req.Website,
		Notes:         req.Notes,
		StreetAddress: req.StreetAddress,
		City:          req.City,
		StateProvince: req.StateProvince,
		PostalCode:    req.PostalCode,
		Rating:        req.Rating,
		IsFavorite:    isFavorite,
		IsActive:      true,
	}

	if err := s.contractorRepo.Create(contractor); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Set specialties if provided
	if len(req.SpecialtyIDs) > 0 {
		if err := s.contractorRepo.SetSpecialties(contractor.ID, req.SpecialtyIDs); err != nil {
			return nil, apperrors.Internal(err)
		}
	}

	// Reload with relations
	contractor, reloadErr := s.contractorRepo.FindByID(contractor.ID)
	if reloadErr != nil {
		return nil, apperrors.Internal(reloadErr)
	}

	resp := responses.NewContractorResponse(contractor)
	return &resp, nil
}

// UpdateContractor updates a contractor
func (s *ContractorService) UpdateContractor(contractorID, userID uint, req *requests.UpdateContractorRequest) (*responses.ContractorResponse, error) {
	contractor, err := s.contractorRepo.FindByID(contractorID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, apperrors.NotFound("error.contractor_not_found")
		}
		return nil, apperrors.Internal(err)
	}

	// Check access
	if !s.hasContractorAccess(contractor, userID) {
		return nil, apperrors.Forbidden("error.contractor_access_denied")
	}

	// Apply updates
	if req.Name != nil {
		contractor.Name = *req.Name
	}
	if req.Company != nil {
		contractor.Company = *req.Company
	}
	if req.Phone != nil {
		contractor.Phone = *req.Phone
	}
	if req.Email != nil {
		contractor.Email = *req.Email
	}
	if req.Website != nil {
		contractor.Website = *req.Website
	}
	if req.Notes != nil {
		contractor.Notes = *req.Notes
	}
	if req.StreetAddress != nil {
		contractor.StreetAddress = *req.StreetAddress
	}
	if req.City != nil {
		contractor.City = *req.City
	}
	if req.StateProvince != nil {
		contractor.StateProvince = *req.StateProvince
	}
	if req.PostalCode != nil {
		contractor.PostalCode = *req.PostalCode
	}
	if req.Rating != nil {
		contractor.Rating = req.Rating
	}
	if req.IsFavorite != nil {
		contractor.IsFavorite = *req.IsFavorite
	}
	// If residence_id is provided, verify the user has access to the NEW residence.
	// This prevents an attacker from reassigning a contractor to someone else's residence.
	if req.ResidenceID != nil {
		hasAccess, err := s.residenceRepo.HasAccess(*req.ResidenceID, userID)
		if err != nil {
			return nil, apperrors.Internal(err)
		}
		if !hasAccess {
			return nil, apperrors.Forbidden("error.residence_access_denied")
		}
	}

	// If residence_id is not sent in the request (nil), it means the user
	// removed the residence association - contractor becomes personal
	contractor.ResidenceID = req.ResidenceID

	if err := s.contractorRepo.Update(contractor); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Update specialties if provided
	if req.SpecialtyIDs != nil {
		if err := s.contractorRepo.SetSpecialties(contractorID, req.SpecialtyIDs); err != nil {
			return nil, apperrors.Internal(err)
		}
	}

	// Reload
	contractor, err = s.contractorRepo.FindByID(contractorID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	resp := responses.NewContractorResponse(contractor)
	return &resp, nil
}

// DeleteContractor soft-deletes a contractor
func (s *ContractorService) DeleteContractor(contractorID, userID uint) error {
	contractor, err := s.contractorRepo.FindByID(contractorID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return apperrors.NotFound("error.contractor_not_found")
		}
		return apperrors.Internal(err)
	}

	// Check access
	if !s.hasContractorAccess(contractor, userID) {
		return apperrors.Forbidden("error.contractor_access_denied")
	}

	if err := s.contractorRepo.Delete(contractorID); err != nil {
		return apperrors.Internal(err)
	}

	return nil
}

// ToggleFavorite toggles the favorite status of a contractor and returns the updated contractor
func (s *ContractorService) ToggleFavorite(contractorID, userID uint) (*responses.ContractorResponse, error) {
	contractor, err := s.contractorRepo.FindByID(contractorID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, apperrors.NotFound("error.contractor_not_found")
		}
		return nil, apperrors.Internal(err)
	}

	// Check access
	if !s.hasContractorAccess(contractor, userID) {
		return nil, apperrors.Forbidden("error.contractor_access_denied")
	}

	_, err = s.contractorRepo.ToggleFavorite(contractorID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// Re-fetch the contractor to get the updated state with all relations
	contractor, err = s.contractorRepo.FindByID(contractorID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	resp := responses.NewContractorResponse(contractor)
	return &resp, nil
}

// GetContractorTasks gets all tasks for a contractor
func (s *ContractorService) GetContractorTasks(contractorID, userID uint) ([]responses.TaskResponse, error) {
	contractor, err := s.contractorRepo.FindByID(contractorID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, apperrors.NotFound("error.contractor_not_found")
		}
		return nil, apperrors.Internal(err)
	}

	// Check access
	if !s.hasContractorAccess(contractor, userID) {
		return nil, apperrors.Forbidden("error.contractor_access_denied")
	}

	tasks, err := s.contractorRepo.GetTasksForContractor(contractorID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	return responses.NewTaskListResponse(tasks), nil
}

// ListContractorsByResidence lists all contractors for a specific residence
func (s *ContractorService) ListContractorsByResidence(residenceID, userID uint) ([]responses.ContractorResponse, error) {
	// Check user has access to the residence
	hasAccess, err := s.residenceRepo.HasAccess(residenceID, userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}
	if !hasAccess {
		return nil, apperrors.Forbidden("error.residence_access_denied")
	}

	contractors, err := s.contractorRepo.FindByResidence(residenceID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	return responses.NewContractorListResponse(contractors), nil
}

// GetSpecialties returns all contractor specialties
func (s *ContractorService) GetSpecialties() ([]responses.ContractorSpecialtyResponse, error) {
	specialties, err := s.contractorRepo.GetAllSpecialties()
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	result := make([]responses.ContractorSpecialtyResponse, len(specialties))
	for i, sp := range specialties {
		result[i] = responses.NewContractorSpecialtyResponse(&sp)
	}
	return result, nil
}
