package handlers

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/Wikid82/charon/backend/internal/services"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func TestAccessListHandler_SetGeoIPService(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	db.AutoMigrate(&models.AccessList{})

	handler := NewAccessListHandler(db)

	// Test setting GeoIP service
	geoipSvc := &services.GeoIPService{}
	handler.SetGeoIPService(geoipSvc)

	// No error or panic means success - the function is a simple setter
	// We can't easily verify the internal state, but we can verify it doesn't panic
}

func TestAccessListHandler_SetGeoIPService_Nil(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	db.AutoMigrate(&models.AccessList{})

	handler := NewAccessListHandler(db)

	// Test setting nil GeoIP service (should not panic)
	handler.SetGeoIPService(nil)
}

func TestAccessListHandler_Get_InvalidID(t *testing.T) {
	router, _ := setupAccessListTestRouter(t)

	req := httptest.NewRequest(http.MethodGet, "/access-lists/invalid", http.NoBody)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAccessListHandler_Update_InvalidID(t *testing.T) {
	router, _ := setupAccessListTestRouter(t)

	body := []byte(`{"name":"Test","type":"whitelist"}`)
	req := httptest.NewRequest(http.MethodPut, "/access-lists/invalid", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAccessListHandler_Update_InvalidJSON(t *testing.T) {
	router, db := setupAccessListTestRouter(t)

	// Create test ACL
	acl := models.AccessList{UUID: "test-uuid", Name: "Test", Type: "whitelist"}
	db.Create(&acl)

	req := httptest.NewRequest(http.MethodPut, "/access-lists/1", bytes.NewReader([]byte("invalid json")))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAccessListHandler_Delete_InvalidID(t *testing.T) {
	router, _ := setupAccessListTestRouter(t)

	req := httptest.NewRequest(http.MethodDelete, "/access-lists/invalid", http.NoBody)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAccessListHandler_TestIP_InvalidID(t *testing.T) {
	router, _ := setupAccessListTestRouter(t)

	body := []byte(`{"ip_address":"192.168.1.1"}`)
	req := httptest.NewRequest(http.MethodPost, "/access-lists/invalid/test", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAccessListHandler_TestIP_MissingIPAddress(t *testing.T) {
	router, db := setupAccessListTestRouter(t)

	// Create test ACL
	acl := models.AccessList{UUID: "test-uuid", Name: "Test", Type: "whitelist"}
	db.Create(&acl)

	body := []byte(`{}`)
	req := httptest.NewRequest(http.MethodPost, "/access-lists/1/test", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAccessListHandler_List_DBError(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	// Don't migrate the table to cause error

	gin.SetMode(gin.TestMode)
	router := gin.New()

	handler := NewAccessListHandler(db)
	router.GET("/access-lists", handler.List)

	req := httptest.NewRequest(http.MethodGet, "/access-lists", http.NoBody)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusInternalServerError, w.Code)
}

func TestAccessListHandler_Get_DBError(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	// Don't migrate the table to cause error

	gin.SetMode(gin.TestMode)
	router := gin.New()

	handler := NewAccessListHandler(db)
	router.GET("/access-lists/:id", handler.Get)

	req := httptest.NewRequest(http.MethodGet, "/access-lists/1", http.NoBody)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Should be 500 since table doesn't exist
	assert.Equal(t, http.StatusInternalServerError, w.Code)
}

func TestAccessListHandler_Delete_InternalError(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	// Migrate AccessList but not ProxyHost to cause internal error on delete
	db.AutoMigrate(&models.AccessList{})

	gin.SetMode(gin.TestMode)
	router := gin.New()

	handler := NewAccessListHandler(db)
	router.DELETE("/access-lists/:id", handler.Delete)

	// Create ACL to delete
	acl := models.AccessList{UUID: "test-uuid", Name: "Test", Type: "whitelist"}
	db.Create(&acl)

	req := httptest.NewRequest(http.MethodDelete, "/access-lists/1", http.NoBody)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Should return 500 since ProxyHost table doesn't exist for checking usage
	assert.Equal(t, http.StatusInternalServerError, w.Code)
}

func TestAccessListHandler_Update_InvalidType(t *testing.T) {
	router, db := setupAccessListTestRouter(t)

	// Create test ACL
	acl := models.AccessList{UUID: "test-uuid", Name: "Test", Type: "whitelist"}
	db.Create(&acl)

	body := []byte(`{"name":"Updated","type":"invalid_type"}`)
	req := httptest.NewRequest(http.MethodPut, "/access-lists/1", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAccessListHandler_Create_InvalidJSON(t *testing.T) {
	router, _ := setupAccessListTestRouter(t)

	req := httptest.NewRequest(http.MethodPost, "/access-lists", bytes.NewReader([]byte("invalid")))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAccessListHandler_TestIP_Blacklist(t *testing.T) {
	router, db := setupAccessListTestRouter(t)

	// Create blacklist ACL
	acl := models.AccessList{
		UUID:    "blacklist-uuid",
		Name:    "Test Blacklist",
		Type:    "blacklist",
		IPRules: `[{"cidr":"10.0.0.0/8","description":"Block 10.x"}]`,
		Enabled: true,
	}
	db.Create(&acl)

	// Test IP in blacklist
	body := []byte(`{"ip_address":"10.0.0.1"}`)
	req := httptest.NewRequest(http.MethodPost, "/access-lists/1/test", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestAccessListHandler_TestIP_GeoWhitelist(t *testing.T) {
	router, db := setupAccessListTestRouter(t)

	// Create geo whitelist ACL
	acl := models.AccessList{
		UUID:         "geo-uuid",
		Name:         "US Only",
		Type:         "geo_whitelist",
		CountryCodes: "US,CA",
		Enabled:      true,
	}
	db.Create(&acl)

	// Test IP (geo lookup will likely fail in test but coverage is what matters)
	body := []byte(`{"ip_address":"8.8.8.8"}`)
	req := httptest.NewRequest(http.MethodPost, "/access-lists/1/test", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestAccessListHandler_TestIP_LocalNetworkOnly(t *testing.T) {
	router, db := setupAccessListTestRouter(t)

	// Create local network only ACL
	acl := models.AccessList{
		UUID:             "local-uuid",
		Name:             "Local Only",
		Type:             "whitelist",
		LocalNetworkOnly: true,
		Enabled:          true,
	}
	db.Create(&acl)

	// Test with local IP
	body := []byte(`{"ip_address":"192.168.1.1"}`)
	req := httptest.NewRequest(http.MethodPost, "/access-lists/1/test", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	// Test with public IP
	body = []byte(`{"ip_address":"8.8.8.8"}`)
	req = httptest.NewRequest(http.MethodPost, "/access-lists/1/test", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w = httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestAccessListHandler_TestIP_InternalError(t *testing.T) {
	// Create DB without migrating AccessList to cause internal error
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	// Don't migrate - this causes a "no such table" error which is an internal error

	gin.SetMode(gin.TestMode)
	router := gin.New()

	handler := NewAccessListHandler(db)
	router.POST("/access-lists/:id/test", handler.TestIP)

	body := []byte(`{"ip_address":"192.168.1.1"}`)
	req := httptest.NewRequest(http.MethodPost, "/access-lists/1/test", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Should return 500 since table doesn't exist (internal error, not ErrAccessListNotFound)
	assert.Equal(t, http.StatusInternalServerError, w.Code)
}
