package services

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

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

func setupSuggestionService(t *testing.T) *SuggestionService {
	db := testutil.SetupTestDB(t)
	residenceRepo := repositories.NewResidenceRepository(db)
	return NewSuggestionService(db, residenceRepo)
}

func createTemplateWithConditions(t *testing.T, service *SuggestionService, title string, conditions interface{}) *models.TaskTemplate {
	t.Helper()
	var condJSON json.RawMessage
	if conditions == nil {
		condJSON = json.RawMessage(`{}`)
	} else {
		b, err := json.Marshal(conditions)
		require.NoError(t, err)
		condJSON = b
	}

	tmpl := &models.TaskTemplate{
		Title:      title,
		IsActive:   true,
		Conditions: condJSON,
	}
	err := service.db.Create(tmpl).Error
	require.NoError(t, err)
	return tmpl
}

func TestSuggestionService_UniversalTemplate(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "Test House")

	// Create universal template (empty conditions)
	createTemplateWithConditions(t, service, "Change Air Filters", nil)

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Equal(t, "Change Air Filters", resp.Suggestions[0].Template.Title)
	assert.Equal(t, baseUniversalScore, resp.Suggestions[0].RelevanceScore)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "universal")
}

func TestSuggestionService_HeatingTypeMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	heatingType := "gas_furnace"
	residence := &models.Residence{
		OwnerID:     user.ID,
		Name:        "Test House",
		IsActive:    true,
		IsPrimary:   true,
		HeatingType: &heatingType,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	// Template that requires gas_furnace
	createTemplateWithConditions(t, service, "Furnace Filter Change", map[string]interface{}{
		"heating_type": "gas_furnace",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Equal(t, stringMatchBonus, resp.Suggestions[0].RelevanceScore)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "heating_type:gas_furnace")
}

func TestSuggestionService_ExcludedWhenPoolRequiredButFalse(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	// Residence with no pool (default false)
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "No Pool House")

	// Template that requires pool
	createTemplateWithConditions(t, service, "Clean Pool", map[string]interface{}{
		"has_pool": true,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Len(t, resp.Suggestions, 0) // Should be excluded
}

func TestSuggestionService_NilFieldIgnored(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	// Residence with no heating_type set (nil)
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "Basic House")

	// Template that has a heating_type condition
	createTemplateWithConditions(t, service, "Furnace Service", map[string]interface{}{
		"heating_type": "gas_furnace",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	// Should be included (not excluded) but with low partial score
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "partial_profile")
}

func TestSuggestionService_ProfileCompleteness(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	heatingType := "gas_furnace"
	coolingType := "central_ac"
	residence := &models.Residence{
		OwnerID:     user.ID,
		Name:        "Partial House",
		IsActive:    true,
		IsPrimary:   true,
		HeatingType: &heatingType,
		CoolingType: &coolingType,
		HasPool:     true,
		HasGarage:   true,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	// Create at least one template so we get a response
	createTemplateWithConditions(t, service, "Universal Task", nil)

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	// 4 fields filled out of 15 (home-profile fields + ZIP/region)
	expectedCompleteness := 4.0 / float64(totalProfileFields)
	assert.InDelta(t, expectedCompleteness, resp.ProfileCompleteness, 0.01)
}

func TestSuggestionService_SortedByScoreDescending(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	heatingType := "gas_furnace"
	residence := &models.Residence{
		OwnerID:     user.ID,
		Name:        "Full House",
		IsActive:    true,
		IsPrimary:   true,
		HeatingType: &heatingType,
		HasPool:     true,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	// Universal template - low score
	createTemplateWithConditions(t, service, "Universal Task", nil)

	// Template with matching heating_type - medium score
	createTemplateWithConditions(t, service, "Furnace Service", map[string]interface{}{
		"heating_type": "gas_furnace",
	})

	// Template with matching pool - high score
	createTemplateWithConditions(t, service, "Pool Clean", map[string]interface{}{
		"has_pool": true,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 3)

	// Verify sorted by score descending
	for i := 0; i < len(resp.Suggestions)-1; i++ {
		assert.GreaterOrEqual(t, resp.Suggestions[i].RelevanceScore, resp.Suggestions[i+1].RelevanceScore)
	}
}

func TestSuggestionService_AccessDenied(t *testing.T) {
	service := setupSuggestionService(t)
	owner := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")
	stranger := testutil.CreateTestUser(t, service.db, "stranger", "stranger@test.com", "password")

	residence := testutil.CreateTestResidence(t, service.db, owner.ID, "Private House")

	_, err := service.GetSuggestions(residence.ID, stranger.ID)
	require.Error(t, err)
	testutil.AssertAppErrorCode(t, err, 403)
}

func TestSuggestionService_MultipleConditionsAllMustMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	heatingType := "gas_furnace"
	residence := &models.Residence{
		OwnerID:     user.ID,
		Name:        "Full House",
		IsActive:    true,
		IsPrimary:   true,
		HeatingType: &heatingType,
		HasPool:     true,
		HasFireplace: true,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	// Template requiring both pool AND fireplace AND heating_type
	createTemplateWithConditions(t, service, "Pool & Fireplace Combo", map[string]interface{}{
		"has_pool":     true,
		"has_fireplace": true,
		"heating_type": "gas_furnace",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)

	// All three conditions matched
	expectedScore := boolMatchBonus + boolMatchBonus + stringMatchBonus // 0.3 + 0.3 + 0.25 = 0.85
	assert.InDelta(t, expectedScore, resp.Suggestions[0].RelevanceScore, 0.01)
	assert.Len(t, resp.Suggestions[0].MatchReasons, 3)
}

// === Malformed conditions JSON treated as universal ===

func TestSuggestionService_MalformedConditions(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "Test House")

	// Create template with malformed JSON conditions
	tmpl := &models.TaskTemplate{
		Title:      "Bad JSON Template",
		IsActive:   true,
		Conditions: json.RawMessage(`{bad json`),
	}
	err := service.db.Create(tmpl).Error
	require.NoError(t, err)

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	// Should be treated as universal
	assert.Equal(t, baseUniversalScore, resp.Suggestions[0].RelevanceScore)
}

// === Null conditions JSON treated as universal ===

func TestSuggestionService_NullConditions(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "Test House")

	tmpl := &models.TaskTemplate{
		Title:      "Null Conditions",
		IsActive:   true,
		Conditions: json.RawMessage(`null`),
	}
	err := service.db.Create(tmpl).Error
	require.NoError(t, err)

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Equal(t, baseUniversalScore, resp.Suggestions[0].RelevanceScore)
}

// === Template with property_type condition ===

func TestSuggestionService_PropertyTypeMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")

	// Create a property type
	propType := &models.ResidenceType{Name: "House"}
	err := service.db.Create(propType).Error
	require.NoError(t, err)

	residence := &models.Residence{
		OwnerID:        user.ID,
		Name:           "My House",
		IsActive:       true,
		IsPrimary:      true,
		PropertyTypeID: &propType.ID,
	}
	err = service.db.Create(residence).Error
	require.NoError(t, err)
	// Reload with PropertyType preloaded
	err = service.db.Preload("PropertyType").First(residence, residence.ID).Error
	require.NoError(t, err)

	createTemplateWithConditions(t, service, "House Task", map[string]interface{}{
		"property_type": "House",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "property_type:House")
}

// === CalculateProfileCompleteness with fully filled profile ===

func TestCalculateProfileCompleteness_FullProfile(t *testing.T) {
	ht := "gas_furnace"
	ct := "central_ac"
	wht := "tank_gas"
	rt := "asphalt_shingle"
	et := "brick"
	fp := "hardwood"
	lt := "lawn"

	residence := &models.Residence{
		HeatingType:        &ht,
		CoolingType:        &ct,
		WaterHeaterType:    &wht,
		RoofType:           &rt,
		HasPool:            true,
		HasSprinklerSystem: true,
		HasSeptic:          true,
		HasFireplace:       true,
		HasGarage:          true,
		HasBasement:        true,
		HasAttic:           true,
		ExteriorType:       &et,
		FlooringPrimary:    &fp,
		LandscapingType:    &lt,
		PostalCode:         "10001", // NY → zone 5 — counts as the 15th field
	}

	completeness := CalculateProfileCompleteness(residence)
	assert.Equal(t, 1.0, completeness)
}

// === CalculateProfileCompleteness with empty profile ===

func TestCalculateProfileCompleteness_EmptyProfile(t *testing.T) {
	residence := &models.Residence{}
	completeness := CalculateProfileCompleteness(residence)
	assert.Equal(t, 0.0, completeness)
}

// === Score capped at 1.0 ===

func TestSuggestionService_ScoreCappedAtOne(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")

	ht := "gas_furnace"
	ct := "central_ac"
	wht := "tank_gas"
	rt := "asphalt_shingle"
	et := "brick"

	residence := &models.Residence{
		OwnerID:         user.ID,
		Name:            "Full House",
		IsActive:        true,
		IsPrimary:       true,
		HeatingType:     &ht,
		CoolingType:     &ct,
		WaterHeaterType: &wht,
		RoofType:        &rt,
		ExteriorType:    &et,
		HasPool:         true,
		HasFireplace:    true,
		HasGarage:       true,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	// Template that matches many fields — score should be capped at 1.0
	createTemplateWithConditions(t, service, "Super Match", map[string]interface{}{
		"heating_type":      "gas_furnace",
		"cooling_type":      "central_ac",
		"water_heater_type": "tank_gas",
		"roof_type":         "asphalt_shingle",
		"exterior_type":     "brick",
		"has_pool":          true,
		"has_fireplace":     true,
		"has_garage":        true,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.LessOrEqual(t, resp.Suggestions[0].RelevanceScore, 1.0)
}

// === Inactive templates are excluded ===

func TestSuggestionService_InactiveTemplateExcluded(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "Test House")

	// Create inactive template via raw SQL to bypass GORM default:true on is_active
	err := service.db.Exec("INSERT INTO task_tasktemplate (title, is_active, conditions, created_at, updated_at) VALUES ('Inactive Task', false, '{}', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)").Error
	require.NoError(t, err)

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Len(t, resp.Suggestions, 0)
}

// === Template excluded when requires sprinkler but residence doesn't have it ===

func TestSuggestionService_ExcludedWhenSprinklerRequired(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "No Sprinkler House")

	createTemplateWithConditions(t, service, "Sprinkler Maintenance", map[string]interface{}{
		"has_sprinkler_system": true,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Len(t, resp.Suggestions, 0)
}

// === All bool field exclusions ===

func TestSuggestionService_ExcludedWhenSepticRequired(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "City House")

	createTemplateWithConditions(t, service, "Septic Pump", map[string]interface{}{
		"has_septic": true,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Len(t, resp.Suggestions, 0)
}

func TestSuggestionService_ExcludedWhenFireplaceRequired(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "No Fireplace")

	createTemplateWithConditions(t, service, "Chimney Sweep", map[string]interface{}{
		"has_fireplace": true,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Len(t, resp.Suggestions, 0)
}

func TestSuggestionService_ExcludedWhenGarageRequired(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "No Garage")

	createTemplateWithConditions(t, service, "Garage Door Service", map[string]interface{}{
		"has_garage": true,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Len(t, resp.Suggestions, 0)
}

func TestSuggestionService_ExcludedWhenBasementRequired(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "Slab Home")

	createTemplateWithConditions(t, service, "Basement Waterproofing", map[string]interface{}{
		"has_basement": true,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Len(t, resp.Suggestions, 0)
}

func TestSuggestionService_ExcludedWhenAtticRequired(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")
	residence := testutil.CreateTestResidence(t, service.db, user.ID, "Flat Roof")

	createTemplateWithConditions(t, service, "Attic Insulation", map[string]interface{}{
		"has_attic": true,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	assert.Len(t, resp.Suggestions, 0)
}

// === String field matches for all remaining types ===

func TestSuggestionService_CoolingTypeMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")

	coolingType := "central_ac"
	residence := &models.Residence{
		OwnerID:     user.ID,
		Name:        "Cool House",
		IsActive:    true,
		IsPrimary:   true,
		CoolingType: &coolingType,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	createTemplateWithConditions(t, service, "AC Filter", map[string]interface{}{
		"cooling_type": "central_ac",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "cooling_type:central_ac")
}

func TestSuggestionService_WaterHeaterTypeMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")

	wht := "tank_gas"
	residence := &models.Residence{
		OwnerID:         user.ID,
		Name:            "Hot Water House",
		IsActive:        true,
		IsPrimary:       true,
		WaterHeaterType: &wht,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	createTemplateWithConditions(t, service, "Flush Water Heater", map[string]interface{}{
		"water_heater_type": "tank_gas",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "water_heater_type:tank_gas")
}

func TestSuggestionService_ExteriorTypeMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")

	et := "brick"
	residence := &models.Residence{
		OwnerID:      user.ID,
		Name:         "Brick House",
		IsActive:     true,
		IsPrimary:    true,
		ExteriorType: &et,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	createTemplateWithConditions(t, service, "Pressure Wash Brick", map[string]interface{}{
		"exterior_type": "brick",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "exterior_type:brick")
}

func TestSuggestionService_FlooringPrimaryMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")

	fp := "hardwood"
	residence := &models.Residence{
		OwnerID:         user.ID,
		Name:            "Hardwood House",
		IsActive:        true,
		IsPrimary:       true,
		FlooringPrimary: &fp,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	createTemplateWithConditions(t, service, "Refinish Floors", map[string]interface{}{
		"flooring_primary": "hardwood",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "flooring_primary:hardwood")
}

func TestSuggestionService_LandscapingTypeMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")

	lt := "lawn"
	residence := &models.Residence{
		OwnerID:         user.ID,
		Name:            "Lawn House",
		IsActive:        true,
		IsPrimary:       true,
		LandscapingType: &lt,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	createTemplateWithConditions(t, service, "Fertilize Lawn", map[string]interface{}{
		"landscaping_type": "lawn",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "landscaping_type:lawn")
}

func TestSuggestionService_RoofTypeMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")

	rt := "asphalt_shingle"
	residence := &models.Residence{
		OwnerID:   user.ID,
		Name:      "Shingle House",
		IsActive:  true,
		IsPrimary: true,
		RoofType:  &rt,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	createTemplateWithConditions(t, service, "Inspect Roof", map[string]interface{}{
		"roof_type": "asphalt_shingle",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "roof_type:asphalt_shingle")
}

// === Mismatch on string field — no score for that field ===

func TestSuggestionService_HeatingTypeMismatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "Password123")

	heatingType := "electric_furnace"
	residence := &models.Residence{
		OwnerID:     user.ID,
		Name:        "Electric House",
		IsActive:    true,
		IsPrimary:   true,
		HeatingType: &heatingType,
	}
	err := service.db.Create(residence).Error
	require.NoError(t, err)

	// Template wants gas_furnace but residence has electric_furnace
	createTemplateWithConditions(t, service, "Gas Furnace Service", map[string]interface{}{
		"heating_type": "gas_furnace",
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	// Should still be included but with partial_profile (no match, no exclude)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "partial_profile")
}

// === templateConditions.isEmpty ===

func TestTemplateConditions_IsEmpty(t *testing.T) {
	cond := &templateConditions{}
	assert.True(t, cond.isEmpty())

	ht := "gas"
	cond2 := &templateConditions{HeatingType: &ht}
	assert.False(t, cond2.isEmpty())

	pool := true
	cond3 := &templateConditions{HasPool: &pool}
	assert.False(t, cond3.isEmpty())

	pt := "House"
	cond4 := &templateConditions{PropertyType: &pt}
	assert.False(t, cond4.isEmpty())

	var regionID uint = 5
	cond5 := &templateConditions{ClimateRegionID: &regionID}
	assert.False(t, cond5.isEmpty())
}

// === Climate region condition (15th field) ===

func TestSuggestionService_ClimateRegionMatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	// NY ZIP 10001 → prefix 100 → NY → zone 5 (Cold)
	residence := &models.Residence{
		OwnerID:    user.ID,
		Name:       "NYC House",
		IsActive:   true,
		IsPrimary:  true,
		PostalCode: "10001",
	}
	require.NoError(t, service.db.Create(residence).Error)

	// Template tagged for zone 5 (Cold)
	createTemplateWithConditions(t, service, "Winterize Sprinkler", map[string]interface{}{
		"climate_region_id": 5,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	assert.InDelta(t, climateRegionBonus, resp.Suggestions[0].RelevanceScore, 0.001)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "climate_region")
}

func TestSuggestionService_ClimateRegionMismatch(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	// FL ZIP 33101 → FL → zone 1 (Hot-Humid)
	residence := &models.Residence{
		OwnerID:    user.ID,
		Name:       "Miami House",
		IsActive:   true,
		IsPrimary:  true,
		PostalCode: "33101",
	}
	require.NoError(t, service.db.Create(residence).Error)

	// Template tagged for zone 6 (Very Cold) — no match
	createTemplateWithConditions(t, service, "Snowblower Service", map[string]interface{}{
		"climate_region_id": 6,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1) // Still included — mismatch doesn't exclude
	assert.InDelta(t, baseUniversalScore*0.5, resp.Suggestions[0].RelevanceScore, 0.001)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "partial_profile")
}

func TestSuggestionService_ClimateRegionIgnoredWhenNoZip(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	// Explicitly blank ZIP — testutil.CreateTestResidence seeds "12345" by
	// default, which maps to NY/zone 5, so we can't reuse the helper here.
	residence := &models.Residence{
		OwnerID:    user.ID,
		Name:       "No ZIP House",
		IsActive:   true,
		IsPrimary:  true,
		PostalCode: "",
	}
	require.NoError(t, service.db.Create(residence).Error)

	createTemplateWithConditions(t, service, "Zone-Specific Task", map[string]interface{}{
		"climate_region_id": 5,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1) // Still included, just no bonus
	assert.InDelta(t, baseUniversalScore*0.5, resp.Suggestions[0].RelevanceScore, 0.001)
}

func TestSuggestionService_ClimateRegionUnknownZip(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	residence := &models.Residence{
		OwnerID:    user.ID,
		Name:       "Garbage ZIP House",
		IsActive:   true,
		IsPrimary:  true,
		PostalCode: "XYZ12", // not a real US ZIP
	}
	require.NoError(t, service.db.Create(residence).Error)

	createTemplateWithConditions(t, service, "Zone-Specific Task", map[string]interface{}{
		"climate_region_id": 5,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	// Unknown ZIP → 0 region → no match, but no crash
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "partial_profile")
}

func TestSuggestionService_ClimateRegionStacksWithOtherConditions(t *testing.T) {
	service := setupSuggestionService(t)
	user := testutil.CreateTestUser(t, service.db, "owner", "owner@test.com", "password")

	heatingType := "gas_furnace"
	residence := &models.Residence{
		OwnerID:     user.ID,
		Name:        "NY Gas House",
		IsActive:    true,
		IsPrimary:   true,
		PostalCode:  "10001", // NY → zone 5
		HeatingType: &heatingType,
	}
	require.NoError(t, service.db.Create(residence).Error)

	createTemplateWithConditions(t, service, "Winterize Gas Furnace", map[string]interface{}{
		"heating_type":      "gas_furnace",
		"climate_region_id": 5,
	})

	resp, err := service.GetSuggestions(residence.ID, user.ID)
	require.NoError(t, err)
	require.Len(t, resp.Suggestions, 1)
	// Both bonuses should apply: stringMatchBonus + climateRegionBonus
	assert.InDelta(t, stringMatchBonus+climateRegionBonus, resp.Suggestions[0].RelevanceScore, 0.001)
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "heating_type:gas_furnace")
	assert.Contains(t, resp.Suggestions[0].MatchReasons, "climate_region")
}
