package integration

import (
	"encoding/json"
	"net/http"
	"testing"

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

// TestIntegration_ContractorSharingFlow tests the complete contractor sharing scenario:
// - Personal contractors are only visible to their creator
// - Residence-tied contractors are visible to all users with access to that residence
func TestIntegration_ContractorSharingFlow(t *testing.T) {
	app := setupContractorTest(t)

	// ========== Setup Users ==========
	// Create user A
	userAToken := app.registerAndLogin(t, "userA", "userA@test.com", "Password123")

	// Create user B
	userBToken := app.registerAndLogin(t, "userB", "userB@test.com", "Password123")

	// ========== User A creates residence C ==========
	residenceBody := map[string]interface{}{
		"name": "Residence C",
	}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, userAToken)
	require.Equal(t, http.StatusCreated, w.Code, "User A should create residence C")

	var residenceResp map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &residenceResp)
	require.NoError(t, err)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceCID := residenceData["id"].(float64)

	// ========== User A shares residence C with User B ==========
	// Generate share code
	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/"+formatID(residenceCID)+"/generate-share-code", nil, userAToken)
	require.Equal(t, http.StatusOK, w.Code, "User A should generate share code")

	var shareResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &shareResp)
	require.NoError(t, err)
	shareCodeObj := shareResp["share_code"].(map[string]interface{})
	shareCode := shareCodeObj["code"].(string)

	// User B joins with code
	joinBody := map[string]interface{}{
		"code": shareCode,
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/join-with-code", joinBody, userBToken)
	require.Equal(t, http.StatusOK, w.Code, "User B should join residence C with share code")

	// Verify User B has access to residence C
	w = app.makeAuthenticatedRequest(t, "GET", "/api/residences/"+formatID(residenceCID), nil, userBToken)
	assert.Equal(t, http.StatusOK, w.Code, "User B should have access to residence C")

	// ========== User A creates personal contractor D (no residence) ==========
	contractorDBody := map[string]interface{}{
		"name":  "Personal Contractor D",
		"phone": "555-1234",
		"notes": "User A's personal contractor",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/contractors", contractorDBody, userAToken)
	require.Equal(t, http.StatusCreated, w.Code, "User A should create personal contractor D")

	var contractorDResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &contractorDResp)
	require.NoError(t, err)
	contractorDID := contractorDResp["id"].(float64)
	assert.Nil(t, contractorDResp["residence_id"], "Contractor D should have no residence (personal)")

	// ========== User B cannot see User A's personal contractor D ==========
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors/"+formatID(contractorDID), nil, userBToken)
	assert.Equal(t, http.StatusForbidden, w.Code, "User B should NOT have access to User A's personal contractor D")

	// ========== User A creates contractor E tied to residence C ==========
	contractorEBody := map[string]interface{}{
		"name":         "Shared Contractor E",
		"phone":        "555-5678",
		"residence_id": uint(residenceCID),
		"notes":        "Contractor for residence C",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/contractors", contractorEBody, userAToken)
	require.Equal(t, http.StatusCreated, w.Code, "User A should create contractor E tied to residence C")

	var contractorEResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &contractorEResp)
	require.NoError(t, err)
	contractorEID := contractorEResp["id"].(float64)
	assert.Equal(t, residenceCID, contractorEResp["residence_id"], "Contractor E should be tied to residence C")

	// ========== User B has access to contractor E ==========
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors/"+formatID(contractorEID), nil, userBToken)
	assert.Equal(t, http.StatusOK, w.Code, "User B should have access to contractor E (tied to shared residence C)")

	// ========== User B creates personal contractor (also named E for clarity - different ID) ==========
	contractorBPersonalBody := map[string]interface{}{
		"name":  "User B Personal Contractor",
		"phone": "555-9999",
		"notes": "User B's personal contractor",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/contractors", contractorBPersonalBody, userBToken)
	require.Equal(t, http.StatusCreated, w.Code, "User B should create personal contractor")

	var contractorBPersonalResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &contractorBPersonalResp)
	require.NoError(t, err)
	contractorBPersonalID := contractorBPersonalResp["id"].(float64)
	assert.Nil(t, contractorBPersonalResp["residence_id"], "User B's personal contractor should have no residence")

	// ========== User A cannot see User B's personal contractor ==========
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors/"+formatID(contractorBPersonalID), nil, userAToken)
	assert.Equal(t, http.StatusForbidden, w.Code, "User A should NOT have access to User B's personal contractor")

	// ========== User B creates contractor F tied to residence C ==========
	contractorFBody := map[string]interface{}{
		"name":         "Shared Contractor F",
		"phone":        "555-4321",
		"residence_id": uint(residenceCID),
		"notes":        "Another contractor for residence C, created by User B",
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/contractors", contractorFBody, userBToken)
	require.Equal(t, http.StatusCreated, w.Code, "User B should create contractor F tied to residence C")

	var contractorFResp map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &contractorFResp)
	require.NoError(t, err)
	contractorFID := contractorFResp["id"].(float64)
	assert.Equal(t, residenceCID, contractorFResp["residence_id"], "Contractor F should be tied to residence C")

	// ========== User A has access to contractor F ==========
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors/"+formatID(contractorFID), nil, userAToken)
	assert.Equal(t, http.StatusOK, w.Code, "User A should have access to contractor F (tied to shared residence C)")

	// ========== Verify both users see shared contractors E and F ==========
	// User A lists contractors
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors", nil, userAToken)
	require.Equal(t, http.StatusOK, w.Code)

	var userAContractors []map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &userAContractors)
	require.NoError(t, err)

	// User A should see: personal contractor D, shared contractors E and F
	userAContractorIDs := extractContractorIDs(userAContractors)
	assert.Contains(t, userAContractorIDs, uint(contractorDID), "User A should see personal contractor D")
	assert.Contains(t, userAContractorIDs, uint(contractorEID), "User A should see shared contractor E")
	assert.Contains(t, userAContractorIDs, uint(contractorFID), "User A should see shared contractor F")
	assert.NotContains(t, userAContractorIDs, uint(contractorBPersonalID), "User A should NOT see User B's personal contractor")

	// User B lists contractors
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors", nil, userBToken)
	require.Equal(t, http.StatusOK, w.Code)

	var userBContractors []map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &userBContractors)
	require.NoError(t, err)

	// User B should see: personal contractor, shared contractors E and F
	userBContractorIDs := extractContractorIDs(userBContractors)
	assert.Contains(t, userBContractorIDs, uint(contractorBPersonalID), "User B should see their personal contractor")
	assert.Contains(t, userBContractorIDs, uint(contractorEID), "User B should see shared contractor E")
	assert.Contains(t, userBContractorIDs, uint(contractorFID), "User B should see shared contractor F")
	assert.NotContains(t, userBContractorIDs, uint(contractorDID), "User B should NOT see User A's personal contractor D")

	// ========== Verify shared contractor count ==========
	// Both users should see exactly 2 shared contractors (E and F)
	sharedForA := countResidenceContractors(userAContractors, residenceCID)
	sharedForB := countResidenceContractors(userBContractors, residenceCID)
	assert.Equal(t, 2, sharedForA, "User A should see 2 contractors tied to residence C")
	assert.Equal(t, 2, sharedForB, "User B should see 2 contractors tied to residence C")
}

// TestIntegration_ContractorAccessWithoutResidenceShare tests that
// a user without residence access cannot see residence-tied contractors
func TestIntegration_ContractorAccessWithoutResidenceShare(t *testing.T) {
	app := setupContractorTest(t)

	// Create two users
	userAToken := app.registerAndLogin(t, "userA", "userA@test.com", "Password123")
	userBToken := app.registerAndLogin(t, "userB", "userB@test.com", "Password123")

	// User A creates a residence
	residenceBody := map[string]interface{}{
		"name": "Private Residence",
	}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, userAToken)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp)
	residenceData := residenceResp["data"].(map[string]interface{})
	residenceID := residenceData["id"].(float64)

	// User A creates a contractor tied to the residence (NOT shared with User B)
	contractorBody := map[string]interface{}{
		"name":         "Private Contractor",
		"residence_id": uint(residenceID),
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/contractors", contractorBody, userAToken)
	require.Equal(t, http.StatusCreated, w.Code)

	var contractorResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &contractorResp)
	contractorID := contractorResp["id"].(float64)

	// User B should NOT be able to access the contractor
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors/"+formatID(contractorID), nil, userBToken)
	assert.Equal(t, http.StatusForbidden, w.Code, "User B should NOT have access to contractor in unshared residence")

	// User B lists contractors - should NOT include the private contractor
	w = app.makeAuthenticatedRequest(t, "GET", "/api/contractors", nil, userBToken)
	require.Equal(t, http.StatusOK, w.Code)

	var userBContractors []map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &userBContractors)

	userBContractorIDs := extractContractorIDs(userBContractors)
	assert.NotContains(t, userBContractorIDs, uint(contractorID), "User B should NOT see contractor from unshared residence")
}

// TestIntegration_ContractorUpdateAndDeleteAccess tests that only users with
// residence access can update/delete residence-tied contractors
func TestIntegration_ContractorUpdateAndDeleteAccess(t *testing.T) {
	app := setupContractorTest(t)

	// Create users
	userAToken := app.registerAndLogin(t, "userA", "userA@test.com", "Password123")
	userBToken := app.registerAndLogin(t, "userB", "userB@test.com", "Password123")
	userCToken := app.registerAndLogin(t, "userC", "userC@test.com", "Password123")

	// User A creates residence and shares with User B (not User C)
	residenceBody := map[string]interface{}{"name": "Shared Residence"}
	w := app.makeAuthenticatedRequest(t, "POST", "/api/residences", residenceBody, userAToken)
	require.Equal(t, http.StatusCreated, w.Code)

	var residenceResp2 map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &residenceResp2)
	residenceData2 := residenceResp2["data"].(map[string]interface{})
	residenceID := residenceData2["id"].(float64)

	// Share with User B
	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/"+formatID(residenceID)+"/generate-share-code", nil, userAToken)
	require.Equal(t, http.StatusOK, w.Code)

	var shareResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &shareResp)
	shareCodeObj := shareResp["share_code"].(map[string]interface{})
	shareCode := shareCodeObj["code"].(string)

	w = app.makeAuthenticatedRequest(t, "POST", "/api/residences/join-with-code", map[string]interface{}{"code": shareCode}, userBToken)
	require.Equal(t, http.StatusOK, w.Code)

	// User A creates contractor tied to residence
	contractorBody := map[string]interface{}{
		"name":         "Test Contractor",
		"residence_id": uint(residenceID),
	}
	w = app.makeAuthenticatedRequest(t, "POST", "/api/contractors", contractorBody, userAToken)
	require.Equal(t, http.StatusCreated, w.Code)

	var contractorResp3 map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &contractorResp3)
	contractorID3 := contractorResp3["id"].(float64)

	// User B (with access) can update the contractor
	// Note: Must include residence_id to keep it tied to the residence
	updateBody := map[string]interface{}{
		"name":         "Updated by User B",
		"residence_id": uint(residenceID),
	}
	w = app.makeAuthenticatedRequest(t, "PUT", "/api/contractors/"+formatID(contractorID3), updateBody, userBToken)
	assert.Equal(t, http.StatusOK, w.Code, "User B should be able to update contractor in shared residence")

	// User C (without access) cannot update the contractor
	updateBody2 := map[string]interface{}{
		"name":         "Hacked by User C",
		"residence_id": uint(residenceID),
	}
	w = app.makeAuthenticatedRequest(t, "PUT", "/api/contractors/"+formatID(contractorID3), updateBody2, userCToken)
	assert.Equal(t, http.StatusForbidden, w.Code, "User C should NOT be able to update contractor")

	// User C cannot delete the contractor
	w = app.makeAuthenticatedRequest(t, "DELETE", "/api/contractors/"+formatID(contractorID3), nil, userCToken)
	assert.Equal(t, http.StatusForbidden, w.Code, "User C should NOT be able to delete contractor")

	// User B (with access) can delete the contractor
	w = app.makeAuthenticatedRequest(t, "DELETE", "/api/contractors/"+formatID(contractorID3), nil, userBToken)
	assert.Equal(t, http.StatusOK, w.Code, "User B should be able to delete contractor in shared residence")
}

// Helper function to extract contractor IDs from response
func extractContractorIDs(contractors []map[string]interface{}) []uint {
	ids := make([]uint, len(contractors))
	for i, c := range contractors {
		ids[i] = uint(c["id"].(float64))
	}
	return ids
}

// Helper function to count contractors tied to a specific residence
func countResidenceContractors(contractors []map[string]interface{}, residenceID float64) int {
	count := 0
	for _, c := range contractors {
		if rid, ok := c["residence_id"].(float64); ok && rid == residenceID {
			count++
		}
	}
	return count
}
