package handler

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

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/gin-gonic/gin"
)

// helper to build gin engine with given handler without auth for focused handler tests
func newTestRouter(h *Handler, register func(*gin.Engine)) *gin.Engine {
	gin.SetMode(gin.TestMode)
	r := gin.New()
	register(r)
	return r
}

// helper to set up sqlmock-backed handler
func newMockHandler(t *testing.T) (*Handler, sqlmock.Sqlmock, func()) {
	t.Helper()
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	h := &Handler{DB: db, JWTSecret: "secret"}
	cleanup := func() { db.Close() }
	return h, mock, cleanup
}

func TestCreateSensor(t *testing.T) {
	h, mock, cleanup := newMockHandler(t)
	defer cleanup()

	mock.ExpectExec("INSERT INTO sensors").
		WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), "temp", "bool", 1).
		WillReturnResult(sqlmock.NewResult(0, 1))

	r := newTestRouter(h, func(r *gin.Engine) {
		r.POST("/sensors", h.CreateSensor)
	})

	body := map[string]interface{}{
		"device_id":    "11111111-1111-1111-1111-111111111111",
		"name":         "temp",
		"type":         "bool",
		"data_type_id": 1,
	}
	buf, _ := json.Marshal(body)
	req := httptest.NewRequest(http.MethodPost, "/sensors", bytes.NewReader(buf))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusCreated {
		t.Fatalf("expected 201 got %d, body=%s", w.Code, w.Body.String())
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("expectations: %v", err)
	}
}

func TestGetSensors(t *testing.T) {
	h, mock, cleanup := newMockHandler(t)
	defer cleanup()

	rows := sqlmock.NewRows([]string{"id", "device_id", "name", "type", "data_type_id", "created_at", "updated_at"}).
		AddRow("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "temp", "bool", 1, time.Now(), time.Now())

	mock.ExpectQuery(`SELECT BIN_TO_UUID\(id\)`).WillReturnRows(rows)

	r := newTestRouter(h, func(r *gin.Engine) {
		r.GET("/sensors", h.GetSensors)
	})

	req := httptest.NewRequest(http.MethodGet, "/sensors", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200 got %d, body=%s", w.Code, w.Body.String())
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("expectations: %v", err)
	}
}

func TestCreateActor(t *testing.T) {
	h, mock, cleanup := newMockHandler(t)
	defer cleanup()

	mock.ExpectExec("INSERT INTO actors").
		WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), "led", "switch", 1).
		WillReturnResult(sqlmock.NewResult(0, 1))

	r := newTestRouter(h, func(r *gin.Engine) {
		r.POST("/actors", h.CreateActor)
	})

	body := map[string]interface{}{
		"device_id":    "11111111-1111-1111-1111-111111111111",
		"name":         "led",
		"type":         "switch",
		"data_type_id": 1,
	}
	buf, _ := json.Marshal(body)
	req := httptest.NewRequest(http.MethodPost, "/actors", bytes.NewReader(buf))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusCreated {
		t.Fatalf("expected 201 got %d, body=%s", w.Code, w.Body.String())
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("expectations: %v", err)
	}
}

func TestGetActors(t *testing.T) {
	h, mock, cleanup := newMockHandler(t)
	defer cleanup()

	rows := sqlmock.NewRows([]string{"id", "device_id", "name", "type", "data_type_id", "created_at", "updated_at"}).
		AddRow("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "led", "switch", 1, time.Now(), time.Now())

	mock.ExpectQuery(`SELECT BIN_TO_UUID\(id\).*FROM actors`).WillReturnRows(rows)

	r := newTestRouter(h, func(r *gin.Engine) {
		r.GET("/actors", h.GetActors)
	})

	req := httptest.NewRequest(http.MethodGet, "/actors", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200 got %d, body=%s", w.Code, w.Body.String())
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("expectations: %v", err)
	}
}

func TestCreateSensorReading(t *testing.T) {
	h, mock, cleanup := newMockHandler(t)
	defer cleanup()

	mock.ExpectExec("INSERT INTO sensor_readings").
		WithArgs(sqlmock.AnyArg(), 42.0, sqlmock.AnyArg()).
		WillReturnResult(sqlmock.NewResult(10, 1))

	r := newTestRouter(h, func(r *gin.Engine) {
		r.POST("/sensor-readings", h.CreateSensorReading)
	})

	body := map[string]interface{}{
		"sensor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
		"value":     42.0,
	}
	buf, _ := json.Marshal(body)
	req := httptest.NewRequest(http.MethodPost, "/sensor-readings", bytes.NewReader(buf))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusCreated {
		t.Fatalf("expected 201 got %d, body=%s", w.Code, w.Body.String())
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("expectations: %v", err)
	}
}

func TestGetSensorReadings(t *testing.T) {
	h, mock, cleanup := newMockHandler(t)
	defer cleanup()

	rows := sqlmock.NewRows([]string{"id", "sensor_id", "value", "value_at"}).
		AddRow(int64(1), "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", 1.23, time.Now())

	mock.ExpectQuery(`SELECT id, BIN_TO_UUID\(sensor_id\)`).WillReturnRows(rows)

	r := newTestRouter(h, func(r *gin.Engine) {
		r.GET("/sensor-readings", h.GetSensorReadings)
	})

	req := httptest.NewRequest(http.MethodGet, "/sensor-readings", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200 got %d, body=%s", w.Code, w.Body.String())
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("expectations: %v", err)
	}
}
