package main

import (
	"bytes"
	"context"
	"database/sql"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	_ "modernc.org/sqlite"
)

// create an in-memory DB with schema/index identical to main
func newTestDB(t *testing.T) *sql.DB {
	t.Helper()
	db, err := sql.Open("sqlite", "file:testdb?mode=memory&cache=shared")
	if err != nil {
		t.Fatalf("open db: %v", err)
	}
	schema := `CREATE TABLE IF NOT EXISTS drone_events (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		drone_id TEXT NOT NULL,
		latitude REAL NOT NULL,
		longitude REAL NOT NULL,
		timestamp TEXT NOT NULL
	);`
	if _, err := db.Exec(schema); err != nil {
		t.Fatalf("schema: %v", err)
	}
	if _, err := db.Exec(`CREATE INDEX IF NOT EXISTS idx_drone_timestamp ON drone_events(drone_id, timestamp);`); err != nil {
		t.Fatalf("index: %v", err)
	}
	return db
}

func TestIngestAndSnapshot(t *testing.T) {
	db := newTestDB(t)
	srv := &Server{db: db}

	// ingest two drones with multiple timestamps
	events := []DroneEvent{
		{"d1", 1.0, 2.0, "2025-12-06T00:00:00Z"},
		{"d1", 1.1, 2.1, "2025-12-06T00:00:10Z"},
		{"d2", 3.0, 4.0, "2025-12-06T00:00:05Z"},
	}
	for _, ev := range events {
		body, _ := json.Marshal(ev)
		req := httptest.NewRequest(http.MethodPost, "/ingest", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		srv.ingestHandler(rec, req)
		if rec.Code != http.StatusCreated {
			t.Fatalf("ingest %v code=%d body=%s", ev, rec.Code, rec.Body.String())
		}
	}

	// snapshot at 00:00:07 should pick d1@00:00:00? no 00:00:10 too new -> d1@0, d2@5
	q := url.Values{}
	q.Set("time", "2025-12-06T00:00:07Z")
	req := httptest.NewRequest(http.MethodGet, "/snapshot?"+q.Encode(), nil)
	rec := httptest.NewRecorder()
	srv.snapshotHandler(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("snapshot code=%d body=%s", rec.Code, rec.Body.String())
	}

	var resp []DroneEvent
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode snapshot: %v", err)
	}
	if len(resp) != 2 {
		t.Fatalf("expected 2 events, got %d", len(resp))
	}
	// map by drone
	got := map[string]DroneEvent{}
	for _, e := range resp {
		got[e.DroneID] = e
	}
	if got["d1"].Timestamp != "2025-12-06T00:00:00Z" {
		t.Fatalf("d1 timestamp mismatch: %v", got["d1"])
	}
	if got["d2"].Timestamp != "2025-12-06T00:00:05Z" {
		t.Fatalf("d2 timestamp mismatch: %v", got["d2"])
	}
}

func TestSnapshotMissingTime(t *testing.T) {
	db := newTestDB(t)
	srv := &Server{db: db}

	req := httptest.NewRequest(http.MethodGet, "/snapshot", nil)
	rec := httptest.NewRecorder()
	srv.snapshotHandler(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", rec.Code)
	}
}

func TestHealth(t *testing.T) {
	db := newTestDB(t)
	srv := &Server{db: db}
	req := httptest.NewRequest(http.MethodGet, "/health", nil)
	rec := httptest.NewRecorder()
	srv.healthHandler(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("health code=%d body=%s", rec.Code, rec.Body.String())
	}
}

func TestIngestBadJSON(t *testing.T) {
	db := newTestDB(t)
	srv := &Server{db: db}
	req := httptest.NewRequest(http.MethodPost, "/ingest", strings.NewReader("{bad"))
	rec := httptest.NewRecorder()
	srv.ingestHandler(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", rec.Code)
	}
}

// Ensure graceful shutdown path does not hang: start a server and shut it down quickly.
func TestGracefulShutdown(t *testing.T) {
	db := newTestDB(t)
	srv := &Server{db: db}
	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", srv.healthHandler)
	testSrv := &http.Server{Addr: "127.0.0.1:0", Handler: mux}

	go func() { _ = testSrv.ListenAndServe() }()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	if err := testSrv.Shutdown(ctx); err != nil {
		t.Fatalf("shutdown: %v", err)
	}
}
