//go:build test

// Package services contains tests for service listing and eligibility endpoints.
//
// Test Coverage:
//   - ServicesHandler: GET /api/services - List all active services for users
//   - ServicesEligibleForUserHandler: GET /api/services/eligible - List services user is eligible for
//     (based on patch test completion for applicable services)
//
// Patch Test Logic: Services with minimum_age_required > 0 require patch test.
// Users who haven't completed a patch test for a service cannot book it.
// Tests verify eligibility filtering works correctly.
package services

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"crussell/db"
	"crussell/handlers/user"
	"crussell/testutils"

	"github.com/go-chi/chi/v5"
)

// createUserWithDOB creates a test user with specified date of birth
func createUserWithDOB(ctx context.Context, q db.Querier, dob string) (string, error) {
	var userID string
	err := q.QueryRow(ctx, `
		INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING id
	`, "Test", "User", "testuser@test.com", "+44770000001", dob, "hash", "verified_email", "email").Scan(&userID)
	return userID, err
}

func makeRequest(handler http.HandlerFunc, method, path string, body interface{}, ctx context.Context) *httptest.ResponseRecorder {
	var req *http.Request
	if body != nil {
		bodyBytes, _ := json.Marshal(body)
		req = httptest.NewRequest(method, path, bytes.NewReader(bodyBytes))
		req.Header.Set("Content-Type", "application/json")
	} else {
		req = httptest.NewRequest(method, path, nil)
	}
	return makeRequestWithContext(handler, req, ctx)
}

// makeRequestWithContext executes request with chi routing context for path params
func makeRequestWithContext(handler http.HandlerFunc, req *http.Request, ctx context.Context) *httptest.ResponseRecorder {
	// Set up chi routing context for path params on top of the tx context
	rctx := chi.NewRouteContext()
	if id, paramName := extractIDFromPath(req.URL.Path); id != "" {
		rctx.URLParams.Add(paramName, id)
	}
	req = req.WithContext(ctx)
	chiCtx := context.WithValue(ctx, chi.RouteCtxKey, rctx)
	req = req.WithContext(chiCtx)

	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)
	return w
}

// extractIDFromPath extracts the ID from URL paths
func extractIDFromPath(path string) (string, string) {
	patterns := []struct {
		prefix    string
		paramName string
	}{
		{"/api/services/eligible-for/", "user_id"},
	}
	for _, p := range patterns {
		if idx := findLastSegment(path, p.prefix); idx >= 0 {
			return path[idx:], p.paramName
		}
	}
	return "", ""
}

func findLastSegment(path, prefix string) int {
	for i := len(path); i >= len(prefix); i-- {
		if i > 0 && path[i-len(prefix):i] == prefix {
			return i
		}
	}
	return -1
}

// TestServices_ListAll verifies that listing all services returns only active services,
// filtering out inactive services from the response.
func TestServices_ListAll(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := tx.Exec(ctx, `
		INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
		VALUES
			('Manicure', 'Basic manicure', 25.00, 30, true, 0),
			('Pedicure', 'Basic pedicure', 30.00, 45, true, 0),
			('Inactive Service', 'Should not appear', 50.00, 60, false, 0)
	`)
	if err != nil {
		t.Fatalf("failed to create services: %v", err)
	}

	handler := http.HandlerFunc(ServicesHandler)
	w := makeRequest(handler, "GET", "/api/services", nil, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var response []ServiceResponse
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}

	if len(response) != 2 {
		t.Errorf("expected 2 services, got %d", len(response))
	}

	found := map[string]bool{}
	for _, s := range response {
		found[s.Name] = true
	}
	if !found["Manicure"] {
		t.Error("expected Manicure in response")
	}
	if !found["Pedicure"] {
		t.Error("expected Pedicure in response")
	}
	if found["Inactive Service"] {
		t.Error("should not include inactive service")
	}
}

// TestServices_EligibleForUser_AgeFilter verifies that eligible services are filtered based on the user's age,
// excluding services with minimum_age_required higher than the user's age.
func TestServices_EligibleForUser_AgeFilter(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	dob := "2006-01-01" // Age 20 in Feb 2026
	userID, err := createUserWithDOB(ctx, tx, dob)
	if err != nil {
		t.Fatalf("failed to create user: %v", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
		VALUES
			('Under 18 Service', 'For minors', 20.00, 30, true, 16),
			('Adult Only Service', 'For adults only', 50.00, 60, true, 21),
			('No Age Restriction', 'Everyone welcome', 30.00, 45, true, 0)
	`)
	if err != nil {
		t.Fatalf("failed to create services: %v", err)
	}

	handler := http.HandlerFunc(ServicesEligibleForUserHandler)
	req := httptest.NewRequest("GET", "/api/services/eligible-for/"+userID, nil)
	w := makeRequestWithContext(handler, req, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var response []ServiceResponse
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}

	if len(response) != 2 {
		t.Errorf("expected 2 services, got %d. Response: %s", len(response), w.Body.String())
	}

	found := map[string]bool{}
	for _, s := range response {
		found[s.Name] = true
	}
	if !found["Under 18 Service"] {
		t.Error("expected Under 18 Service in response (age 20 >= 16)")
	}
	if !found["No Age Restriction"] {
		t.Error("expected No Age Restriction in response")
	}
	if found["Adult Only Service"] {
		t.Error("should not include Adult Only Service (age 20 < 21)")
	}
}

// TestServices_EligibleForUser_PatchTest verifies that services requiring patch tests include a
// PatchTestStatus field set to 'ok' when the user has completed a valid patch test for that service.
func TestServices_EligibleForUser_PatchTest(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	dob := "2000-01-01"
	userID, err := createUserWithDOB(ctx, tx, dob)
	if err != nil {
		t.Fatalf("failed to create user: %v", err)
	}

	// Create a regular service (no patch test required)
	_, err = tx.Exec(ctx, `
		INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
		VALUES ('Regular Service', 'No patch test needed', 30.00, 30, true, 0)
	`)
	if err != nil {
		t.Fatalf("failed to create regular service: %v", err)
	}

	// Create a service that will require a patch test
	var patchTestSvcID string
	err = tx.QueryRow(ctx, `
		INSERT INTO services (name, description, price, duration_minutes, is_active, minimum_age_required)
		VALUES ('Patch Test Required', 'Requires patch test', 75.00, 60, true, 0)
		RETURNING id
	`).Scan(&patchTestSvcID)
	if err != nil {
		t.Fatalf("failed to create patch test service: %v", err)
	}

	// Create a patch test that links to this service
	var patchTestID string
	err = tx.QueryRow(ctx, `
		INSERT INTO patch_tests (name, description, notice_duration_hours, expiry_months, service_ids)
		VALUES ('Allergy Test', 'Patch test for gel products', 24, 6, $1)
		RETURNING id
	`, []string{patchTestSvcID}).Scan(&patchTestID)
	if err != nil {
		t.Fatalf("failed to create patch test: %v", err)
	}

	// Create a valid user patch test record (tested 24+ hours ago, within expiry)
	_, err = tx.Exec(ctx, `
		INSERT INTO user_patch_tests (user_id, patch_test_id, tested_at)
		VALUES ($1, $2, NOW() - INTERVAL '48 hours')
	`, userID, patchTestID)
	if err != nil {
		t.Fatalf("failed to create user patch test record: %v", err)
	}

	handler := http.HandlerFunc(ServicesEligibleForUserHandler)
	req := httptest.NewRequest("GET", "/api/services/eligible-for/"+userID, nil)
	w := makeRequestWithContext(handler, req, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var response []ServiceResponse
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}

	if len(response) != 2 {
		t.Errorf("expected 2 services, got %d. Response: %s", len(response), w.Body.String())
	}

	var patchTestSvc *ServiceResponse
	var regularSvc *ServiceResponse
	for i := range response {
		if response[i].Name == "Patch Test Required" {
			patchTestSvc = &response[i]
		}
		if response[i].Name == "Regular Service" {
			regularSvc = &response[i]
		}
	}

	if patchTestSvc == nil {
		t.Fatal("Patch Test Required service not found in response")
	}
	if patchTestSvc.PatchTestStatus == nil || *patchTestSvc.PatchTestStatus != "ok" {
		t.Errorf("expected patch test status 'ok', got %v", patchTestSvc.PatchTestStatus)
	}

	if regularSvc == nil {
		t.Fatal("Regular Service not found in response")
	}
	if regularSvc.PatchTestStatus != nil {
		t.Errorf("expected no patch test status for regular service, got %v", regularSvc.PatchTestStatus)
	}
}

// TestContact_ReturnsInfo verifies that the contact info endpoint returns the salon's contact
// details (name, phone, email, role) from the first admin user in the database.
func TestContact_ReturnsInfo(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	_, err := tx.Exec(ctx, `
		INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
		VALUES ('John', 'Smith', 'john@test.com', '+447700000001', '1990-01-01', 'hash', 'admin', 'email')
	`)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	handler := http.HandlerFunc(user.GetContactInfoHandler)
	w := makeRequest(handler, "GET", "/api/contact", nil, ctx)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var response user.ContactInfo
	if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}

	if response.Name == "" {
		t.Error("expected name in response")
	}
	if response.Phone == "" {
		t.Error("expected phone in response")
	}
	if response.Email == "" {
		t.Error("expected email in response")
	}
	if response.Role == "" {
		t.Error("expected role in response")
	}
}

func strPtr(s string) *string {
	return &s
}
