package main

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

	"github.com/go-redis/redis/v8"

	"trip-planner/internal/routing"
	"trip-planner/internal/yandex"
)

func newMockHandlerContext() *HandlerContext {
	redisClient := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})

	// Create an empty routing graph
	router := routing.NewGraph()

	// Create Yandex client
	yandexClient := yandex.NewClient("test-key")

	return NewHandlerContext(redisClient, router, yandexClient)
}

func TestHandlerCityAutocomplete(t *testing.T) {
	h := newMockHandlerContext()
	req := httptest.NewRequest("GET", "/v1/cities?query=mos", nil)
	rr := httptest.NewRecorder()
	CityAutocomplete(h, rr, req)

	if rr.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", rr.Code)
	}

	var resp []cityResponse
	if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}
	t.Logf("city autocomplete response: %d cities", len(resp))
}

func TestHandlerCityStations(t *testing.T) {
	h := newMockHandlerContext()
	req := httptest.NewRequest("GET", "/v1/cities/1/stations", nil)
	rr := httptest.NewRecorder()
	CityStations(h, rr, req)

	if rr.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", rr.Code)
	}
}

func TestHandlerRouteSearch(t *testing.T) {
	h := newMockHandlerContext()

	// Add nodes and edges to the graph to test route finding
	graph := routing.NewGraph()
	graph.AddNode(&routing.Node{ID: "c146", Type: routing.NodeTypeCity, Name: "Simferopol"})
	graph.AddNode(&routing.Node{ID: "c213", Type: routing.NodeTypeCity, Name: "Moscow"})
	graph.AddNode(&routing.Node{ID: "s9600213", Type: routing.NodeTypeStation, Name: "Шереметьево", CityCode: "c146"})
	graph.AddNode(&routing.Node{ID: "s9600396", Type: routing.NodeTypeStation, Name: "Симферополь", CityCode: "c146"})

	// Add synthetic edges: station <-> city
	graph.AddEdge(&routing.Edge{
		From:       graph.Nodes()[2], // s9600213
		To:         graph.Nodes()[0], // c146
		Kind:       routing.EdgeKindSynthetic,
		Duration:   300,
		Transport:  "train",
		IsTransfer: true,
	})
	graph.AddEdge(&routing.Edge{
		From:       graph.Nodes()[0], // c146
		To:         graph.Nodes()[2], // s9600213
		Kind:       routing.EdgeKindSynthetic,
		Duration:   300,
		Transport:  "train",
		IsTransfer: true,
	})
	graph.AddEdge(&routing.Edge{
		From:       graph.Nodes()[3], // s9600396
		To:         graph.Nodes()[0], // c146
		Kind:       routing.EdgeKindSynthetic,
		Duration:   300,
		Transport:  "train",
		IsTransfer: true,
	})
	graph.AddEdge(&routing.Edge{
		From:       graph.Nodes()[0], // c146
		To:         graph.Nodes()[3], // s9600396
		Kind:       routing.EdgeKindSynthetic,
		Duration:   300,
		Transport:  "train",
		IsTransfer: true,
	})

	// Replace the router with our test graph
	h.Router = graph

	// Create request with JSON body
	req := httptest.NewRequest("POST", "/v1/routes/search", strings.NewReader(`{"from_city_id": "c146", "to_city_id": "c213", "date": "2026-08-15"}`))
	req.Header.Set("Content-Type", "application/json")

	rr := httptest.NewRecorder()
	RouteSearch(h, rr, req)

	if rr.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", rr.Code)
	}

	var resp routeSearchResponse
	if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}
	t.Logf("route search response: routes=%+v, count=%d", resp.Routes, resp.Count)
}

func TestHandlerRouteGeoJSON(t *testing.T) {
	h := newMockHandlerContext()

	req := httptest.NewRequest("GET", "/v1/routes/search-123/route-456/geojson", nil)
	rr := httptest.NewRecorder()
	RouteGeoJSON(h, rr, req)

	if rr.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", rr.Code)
	}

	var resp routeGeoJSONResponse
	if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}
	t.Logf("route geojson response: %+v", resp)
}

func TestHandlerStationStatus(t *testing.T) {
	h := newMockHandlerContext()

	req := httptest.NewRequest("GET", "/v1/stations/s9600213/status", nil)
	rr := httptest.NewRecorder()
	StationStatus(h, rr, req)

	if rr.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", rr.Code)
	}

	var resp stationStatusResponse
	if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}
	t.Logf("station status response: %+v", resp)
}

// TestHandlerRouteSearchIntegration tests the route search handler with a fully built graph,
// verifying the cache-aware flow: handler → graph → route search → response.
func TestHandlerRouteSearchIntegration(t *testing.T) {
	h := newMockHandlerContext()

	// Build a routing graph using the same pattern as TestFindRouteSuccess:
	// stations with real edges and one synthetic transfer edge, plus city hub.
	graph := routing.NewGraph()
	graph.AddNode(&routing.Node{ID: "c1", Type: routing.NodeTypeCity, Name: "City Hub"})
	graph.AddNode(&routing.Node{ID: "s1", Type: routing.NodeTypeStation, Name: "Moscow", CityCode: "c1"})
	graph.AddNode(&routing.Node{ID: "s2", Type: routing.NodeTypeStation, Name: "Tula", CityCode: "c1"})
	graph.AddNode(&routing.Node{ID: "s3", Type: routing.NodeTypeStation, Name: "Vladimir", CityCode: "c1"})

	// Add synthetic edge: city hub <-> station Moscow (transfer)
	graph.AddEdge(&routing.Edge{
		From:       graph.Nodes()[0], // c1 city hub
		To:         graph.Nodes()[1], // s1 Moscow
		Kind:       routing.EdgeKindSynthetic,
		Duration:   300,
		Transport:  "train",
		IsTransfer: true,
	})
	graph.AddEdge(&routing.Edge{
		From:       graph.Nodes()[1], // s1 Moscow
		To:         graph.Nodes()[0], // c1 city hub
		Kind:       routing.EdgeKindSynthetic,
		Duration:   300,
		Transport:  "train",
		IsTransfer: true,
	})

	// Add real edge: direct route Moscow → Tula
	graph.AddEdge(&routing.Edge{
		From:       graph.Nodes()[1], // s1 Moscow
		To:         graph.Nodes()[2], // s2 Tula
		Kind:       routing.EdgeKindReal,
		Duration:   3600,
		Transport:  "train",
		IsTransfer: false,
	})

	// Add synthetic transfer edge: Tula → Vladimir (1 transfer)
	graph.AddEdge(&routing.Edge{
		From:       graph.Nodes()[2], // s2 Tula
		To:         graph.Nodes()[3], // s3 Vladimir
		Kind:       routing.EdgeKindSynthetic,
		Duration:   1800,
		Transport:  "train",
		IsTransfer: true,
	})

	// Replace the router with our test graph
	h.Router = graph

	// Create request: from city c1 (Moscow) to city c1 (same city code)
	req := httptest.NewRequest("POST", "/v1/routes/search", strings.NewReader(`{"from_city_id": "c1", "to_city_id": "c1", "date": "2026-08-15"}`))
	req.Header.Set("Content-Type", "application/json")

	rr := httptest.NewRecorder()
	RouteSearch(h, rr, req)

	if rr.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", rr.Code)
	}

	var resp routeSearchResponse
	if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}
	t.Logf("route search response: routes=%+v, count=%d", resp.Routes, resp.Count)

	// With this graph, we should find a route with 1 transfer
	if resp.Count == 0 {
		t.Error("expected at least 1 route, got 0")
	}
}

// TestHandlerRouteSearchNoRoute tests route search when origin/destination not in graph.
func TestHandlerRouteSearchNoRoute(t *testing.T) {
	h := newMockHandlerContext()

	// Create graph with no relevant nodes, but add some so the handler can find
	// the city IDs (otherwise handler returns 404 before route search)
	graph := routing.NewGraph()
	graph.AddNode(&routing.Node{ID: "c999", Type: routing.NodeTypeCity, Name: "City 999"})
	graph.AddNode(&routing.Node{ID: "c888", Type: routing.NodeTypeCity, Name: "City 888"})
	h.Router = graph

	req := httptest.NewRequest("POST", "/v1/routes/search", strings.NewReader(`{"from_city_id": "c999", "to_city_id": "c888", "date": "2026-08-15"}`))
	req.Header.Set("Content-Type", "application/json")

	rr := httptest.NewRecorder()
	RouteSearch(h, rr, req)

	if rr.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", rr.Code)
	}

	var resp routeSearchResponse
	if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}
	if resp.Count != 0 {
		t.Errorf("expected 0 routes, got %d", resp.Count)
	}
}
