package proxy

import (
	"testing"
	"time"
)

func TestIsDocumentRequest(t *testing.T) {
	tests := []struct {
		name     string
		entry    HTTPLogEntry
		expected bool
	}{
		{
			name: "HTML content type",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/",
				ResponseHeaders: map[string]string{
					"Content-Type": "text/html; charset=utf-8",
				},
			},
			expected: true,
		},
		{
			name: "HTML content type lowercase key",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/",
				ResponseHeaders: map[string]string{
					"content-type": "text/html",
				},
			},
			expected: true,
		},
		{
			name: "URL ending in .html",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/page.html",
				ResponseHeaders: map[string]string{
					"Content-Type": "application/octet-stream",
				},
			},
			expected: true,
		},
		{
			name: "JavaScript file",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/script.js",
				ResponseHeaders: map[string]string{
					"Content-Type": "application/javascript",
				},
			},
			expected: false,
		},
		{
			name: "CSS file",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/style.css",
				ResponseHeaders: map[string]string{
					"Content-Type": "text/css",
				},
			},
			expected: false,
		},
		{
			name: "Image PNG",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/image.png",
				ResponseHeaders: map[string]string{
					"Content-Type": "image/png",
				},
			},
			expected: false,
		},
		{
			name: "JSON API response",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/api/users",
				ResponseHeaders: map[string]string{
					"Content-Type": "application/json",
				},
			},
			expected: false, // Fixed: API endpoints should NOT be documents
		},
		{
			name: "Root path with no content type",
			entry: HTTPLogEntry{
				Method:          "GET",
				URL:             "/",
				ResponseHeaders: map[string]string{},
			},
			expected: true,
		},
		{
			name: "POST request to API",
			entry: HTTPLogEntry{
				Method: "POST",
				URL:    "/api/login",
				ResponseHeaders: map[string]string{
					"Content-Type": "application/json",
				},
			},
			expected: false, // POST is not a GET, so should be false
		},
		{
			name: "API path /api/meals/favorites",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/api/meals/favorites",
				ResponseHeaders: map[string]string{
					"Content-Type": "application/json",
				},
			},
			expected: false, // API path should not be a document
		},
		{
			name: "API path with query params",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/api/meals/recent?limit=10",
				ResponseHeaders: map[string]string{
					"Content-Type": "application/json",
				},
			},
			expected: false, // API path should not be a document
		},
		{
			name: "API path /v1/users",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/v1/users",
				ResponseHeaders: map[string]string{
					"Content-Type": "application/json",
				},
			},
			expected: false, // Versioned API path
		},
		{
			name: "GraphQL endpoint",
			entry: HTTPLogEntry{
				Method: "POST",
				URL:    "/graphql",
				ResponseHeaders: map[string]string{
					"Content-Type": "application/json",
				},
			},
			expected: false, // GraphQL endpoint
		},
		{
			name: "XHR request header",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/data",
				RequestHeaders: map[string]string{
					"X-Requested-With": "XMLHttpRequest",
				},
				ResponseHeaders: map[string]string{
					"Content-Type": "application/json",
				},
			},
			expected: false, // XHR requests are not documents
		},
		{
			name: "Accept header prefers JSON",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/data",
				RequestHeaders: map[string]string{
					"Accept": "application/json",
				},
				ResponseHeaders: map[string]string{
					"Content-Type": "application/json",
				},
			},
			expected: false, // Accept JSON means not a document
		},
		{
			name: "Accept header includes both HTML and JSON",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/page",
				RequestHeaders: map[string]string{
					"Accept": "text/html, application/json",
				},
				ResponseHeaders: map[string]string{
					"Content-Type": "text/html",
				},
			},
			expected: true, // HTML response with HTML in Accept
		},
		{
			name: "text/json content type",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/data",
				ResponseHeaders: map[string]string{
					"Content-Type": "text/json",
				},
			},
			expected: false, // JSON is not a document
		},
		{
			name: "REST API path",
			entry: HTTPLogEntry{
				Method: "GET",
				URL:    "/rest/v2/items",
				ResponseHeaders: map[string]string{
					"Content-Type": "application/json",
				},
			},
			expected: false, // REST API path
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isDocumentRequest(tt.entry)
			if result != tt.expected {
				t.Errorf("isDocumentRequest() = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestPageTracker_TrackHTTPRequest_DocumentCreatesSession(t *testing.T) {
	pt := NewPageTracker(100, 5*time.Minute)

	// Track an HTML document request
	entry := HTTPLogEntry{
		ID:        "req-1",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/",
		ResponseHeaders: map[string]string{
			"Content-Type": "text/html; charset=utf-8",
		},
		StatusCode: 200,
	}

	pt.TrackHTTPRequest(entry)

	sessions := pt.GetActiveSessions()
	if len(sessions) != 1 {
		t.Errorf("Expected 1 session, got %d", len(sessions))
	}

	if sessions[0].URL != entry.URL {
		t.Errorf("Expected URL %s, got %s", entry.URL, sessions[0].URL)
	}
}

func TestPageTracker_TrackHTTPRequest_ResourceAddedToSession(t *testing.T) {
	pt := NewPageTracker(100, 5*time.Minute)

	// Track an HTML document request first
	docEntry := HTTPLogEntry{
		ID:        "req-1",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/page",
		ResponseHeaders: map[string]string{
			"Content-Type": "text/html; charset=utf-8",
		},
		StatusCode: 200,
	}
	pt.TrackHTTPRequest(docEntry)

	// Track a resource request with Referer header
	resourceEntry := HTTPLogEntry{
		ID:        "req-2",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/script.js",
		RequestHeaders: map[string]string{
			"Referer": "http://localhost:8080/page",
		},
		ResponseHeaders: map[string]string{
			"Content-Type": "application/javascript",
		},
		StatusCode: 200,
	}
	pt.TrackHTTPRequest(resourceEntry)

	sessions := pt.GetActiveSessions()
	if len(sessions) != 1 {
		t.Errorf("Expected 1 session, got %d", len(sessions))
	}

	if len(sessions[0].Resources) != 1 {
		t.Errorf("Expected 1 resource, got %d", len(sessions[0].Resources))
	}
}

func TestPageTracker_SessionTimeout(t *testing.T) {
	// Create tracker with very short timeout
	pt := NewPageTracker(100, 1*time.Millisecond)

	entry := HTTPLogEntry{
		ID:        "req-1",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/",
		ResponseHeaders: map[string]string{
			"Content-Type": "text/html",
		},
		StatusCode: 200,
	}
	pt.TrackHTTPRequest(entry)

	// Wait for timeout
	time.Sleep(5 * time.Millisecond)

	sessions := pt.GetActiveSessions()
	// Session should still exist but be marked inactive
	if len(sessions) == 0 {
		t.Logf("No sessions returned - GetActiveSessions only returns sessions within timeout")
		return
	}
	if len(sessions) != 1 {
		t.Errorf("Expected 1 session (inactive), got %d", len(sessions))
		return
	}
	if sessions[0].Active {
		t.Errorf("Expected session to be inactive after timeout")
	}
}

func TestPageTracker_Clear(t *testing.T) {
	pt := NewPageTracker(100, 5*time.Minute)

	entry := HTTPLogEntry{
		ID:        "req-1",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/",
		ResponseHeaders: map[string]string{
			"Content-Type": "text/html",
		},
		StatusCode: 200,
	}
	pt.TrackHTTPRequest(entry)

	sessions := pt.GetActiveSessions()
	if len(sessions) != 1 {
		t.Fatalf("Expected 1 session before clear, got %d", len(sessions))
	}

	pt.Clear()

	sessions = pt.GetActiveSessions()
	if len(sessions) != 0 {
		t.Errorf("Expected 0 sessions after clear, got %d", len(sessions))
	}
}

func TestExtractBrowserSessionID(t *testing.T) {
	tests := []struct {
		name     string
		headers  map[string]string
		expected string
	}{
		{
			name:     "No cookie header",
			headers:  map[string]string{},
			expected: "",
		},
		{
			name: "Cookie header with session ID",
			headers: map[string]string{
				"Cookie": "__devtool_sid=sess-abc123",
			},
			expected: "sess-abc123",
		},
		{
			name: "Cookie header lowercase",
			headers: map[string]string{
				"cookie": "__devtool_sid=sess-xyz789",
			},
			expected: "sess-xyz789",
		},
		{
			name: "Multiple cookies",
			headers: map[string]string{
				"Cookie": "other=value; __devtool_sid=sess-multi; another=thing",
			},
			expected: "sess-multi",
		},
		{
			name: "Session ID at start",
			headers: map[string]string{
				"Cookie": "__devtool_sid=sess-first; other=value",
			},
			expected: "sess-first",
		},
		{
			name: "Session ID at end",
			headers: map[string]string{
				"Cookie": "other=value; __devtool_sid=sess-last",
			},
			expected: "sess-last",
		},
		{
			name: "No session ID cookie",
			headers: map[string]string{
				"Cookie": "other=value; something=else",
			},
			expected: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := extractBrowserSessionID(tt.headers)
			if result != tt.expected {
				t.Errorf("extractBrowserSessionID() = %q, want %q", result, tt.expected)
			}
		})
	}
}

func TestPageTracker_BrowserSessionMerging(t *testing.T) {
	pt := NewPageTracker(100, 5*time.Minute)
	browserSessionID := "sess-test123"

	// First navigation to /
	entry1 := HTTPLogEntry{
		ID:        "req-1",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/",
		RequestHeaders: map[string]string{
			"Cookie": "__devtool_sid=" + browserSessionID,
		},
		ResponseHeaders: map[string]string{
			"Content-Type": "text/html; charset=utf-8",
		},
		StatusCode: 200,
	}
	pt.TrackHTTPRequest(entry1)

	sessions := pt.GetActiveSessions()
	if len(sessions) != 1 {
		t.Fatalf("Expected 1 session after first navigation, got %d", len(sessions))
	}
	firstSessionID := sessions[0].ID

	// Second navigation to /login (same browser tab)
	entry2 := HTTPLogEntry{
		ID:        "req-2",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/login",
		RequestHeaders: map[string]string{
			"Cookie": "__devtool_sid=" + browserSessionID,
		},
		ResponseHeaders: map[string]string{
			"Content-Type": "text/html; charset=utf-8",
		},
		StatusCode: 200,
	}
	pt.TrackHTTPRequest(entry2)

	sessions = pt.GetActiveSessions()
	if len(sessions) != 1 {
		t.Fatalf("Expected still 1 session after second navigation, got %d", len(sessions))
	}
	if sessions[0].ID != firstSessionID {
		t.Errorf("Session ID changed: was %s, now %s", firstSessionID, sessions[0].ID)
	}
	if sessions[0].URL != "http://localhost:8080/login" {
		t.Errorf("Expected URL to be updated to /login, got %s", sessions[0].URL)
	}

	// Third navigation back to / (same browser tab)
	entry3 := HTTPLogEntry{
		ID:        "req-3",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/",
		RequestHeaders: map[string]string{
			"Cookie": "__devtool_sid=" + browserSessionID,
		},
		ResponseHeaders: map[string]string{
			"Content-Type": "text/html; charset=utf-8",
		},
		StatusCode: 200,
	}
	pt.TrackHTTPRequest(entry3)

	sessions = pt.GetActiveSessions()
	if len(sessions) != 1 {
		t.Fatalf("Expected still 1 session after third navigation, got %d", len(sessions))
	}
	if sessions[0].ID != firstSessionID {
		t.Errorf("Session ID changed: was %s, now %s", firstSessionID, sessions[0].ID)
	}
	if sessions[0].URL != "http://localhost:8080/" {
		t.Errorf("Expected URL to be updated back to /, got %s", sessions[0].URL)
	}

	// Verify navigation history
	if len(sessions[0].Navigations) != 3 {
		t.Errorf("Expected 3 navigations, got %d", len(sessions[0].Navigations))
	}
}

func TestPageTracker_DifferentBrowserSessions(t *testing.T) {
	pt := NewPageTracker(100, 5*time.Minute)

	// Tab 1 navigates to /
	entry1 := HTTPLogEntry{
		ID:        "req-1",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/",
		RequestHeaders: map[string]string{
			"Cookie": "__devtool_sid=sess-tab1",
		},
		ResponseHeaders: map[string]string{
			"Content-Type": "text/html; charset=utf-8",
		},
		StatusCode: 200,
	}
	pt.TrackHTTPRequest(entry1)

	// Tab 2 navigates to /login (different browser tab)
	entry2 := HTTPLogEntry{
		ID:        "req-2",
		Timestamp: time.Now(),
		Method:    "GET",
		URL:       "http://localhost:8080/login",
		RequestHeaders: map[string]string{
			"Cookie": "__devtool_sid=sess-tab2",
		},
		ResponseHeaders: map[string]string{
			"Content-Type": "text/html; charset=utf-8",
		},
		StatusCode: 200,
	}
	pt.TrackHTTPRequest(entry2)

	sessions := pt.GetActiveSessions()
	if len(sessions) != 2 {
		t.Fatalf("Expected 2 sessions for 2 different tabs, got %d", len(sessions))
	}

	// Verify each session has correct browser session ID
	sessionsByBrowserID := make(map[string]*PageSession)
	for _, s := range sessions {
		sessionsByBrowserID[s.BrowserSession] = s
	}

	if tab1 := sessionsByBrowserID["sess-tab1"]; tab1 == nil {
		t.Error("Missing session for tab1")
	} else if tab1.URL != "http://localhost:8080/" {
		t.Errorf("Tab1 URL = %s, want /", tab1.URL)
	}

	if tab2 := sessionsByBrowserID["sess-tab2"]; tab2 == nil {
		t.Error("Missing session for tab2")
	} else if tab2.URL != "http://localhost:8080/login" {
		t.Errorf("Tab2 URL = %s, want /login", tab2.URL)
	}
}

func TestIsAPIPath(t *testing.T) {
	tests := []struct {
		url      string
		expected bool
	}{
		{"/api/users", true},
		{"/api/meals/favorites", true},
		{"/api/meals/recent?limit=10", true},
		{"/v1/users", true},
		{"/v2/products", true},
		{"/v3/orders/123", true},
		{"/graphql", true},
		{"/graphql/query", true},
		{"/rest/items", true},
		{"/_api/search", true},
		{"/ajax/load", true},
		{"/", false},
		{"/page", false},
		{"/login", false},
		{"/users/profile", false},
		{"/dashboard", false},
		{"/about", false},
		{"http://localhost/api/users", true},
		{"https://example.com/v1/items", true},
	}

	for _, tt := range tests {
		t.Run(tt.url, func(t *testing.T) {
			result := isAPIPath(tt.url)
			if result != tt.expected {
				t.Errorf("isAPIPath(%q) = %v, want %v", tt.url, result, tt.expected)
			}
		})
	}
}

func TestHasResourceExtension(t *testing.T) {
	tests := []struct {
		url      string
		expected bool
	}{
		{"/script.js", true},
		{"/style.css", true},
		{"/image.png", true},
		{"/image.jpg", true},
		{"/font.woff2", true},
		{"/data.json", true},
		{"/page.html", false}, // .html is NOT a resource extension
		{"/", false},
		{"/api/users", false},
		{"/page", false},
		{"/api/v1/users.json", true},    // Contains .json
		{"/path/to/resource.jsx", true}, // Contains .js substring
	}

	for _, tt := range tests {
		t.Run(tt.url, func(t *testing.T) {
			result := hasResourceExtension(tt.url)
			if result != tt.expected {
				t.Errorf("hasResourceExtension(%q) = %v, want %v", tt.url, result, tt.expected)
			}
		})
	}
}
