package services

import (
	"fmt"
	"net"
	"testing"

	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func setupProxyHostTestDB(t *testing.T) *gorm.DB {
	dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}))
	return db
}

func TestProxyHostService_ValidateUniqueDomain(t *testing.T) {
	db := setupProxyHostTestDB(t)
	service := NewProxyHostService(db)

	// Create existing host
	existing := &models.ProxyHost{
		DomainNames: "example.com",
		ForwardHost: "127.0.0.1",
		ForwardPort: 8080,
	}
	require.NoError(t, db.Create(existing).Error)

	tests := []struct {
		name        string
		domainNames string
		excludeID   uint
		wantErr     bool
	}{
		{
			name:        "New unique domain",
			domainNames: "new.example.com",
			excludeID:   0,
			wantErr:     false,
		},
		{
			name:        "Duplicate domain",
			domainNames: "example.com",
			excludeID:   0,
			wantErr:     true,
		},
		{
			name:        "Same domain but excluded ID (update self)",
			domainNames: "example.com",
			excludeID:   existing.ID,
			wantErr:     false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := service.ValidateUniqueDomain(tt.domainNames, tt.excludeID)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestProxyHostService_CRUD(t *testing.T) {
	db := setupProxyHostTestDB(t)
	service := NewProxyHostService(db)

	// Create
	host := &models.ProxyHost{
		UUID:        "uuid-1",
		DomainNames: "test.example.com",
		ForwardHost: "127.0.0.1",
		ForwardPort: 8080,
	}
	err := service.Create(host)
	assert.NoError(t, err)
	assert.NotZero(t, host.ID)

	// Create Duplicate
	dup := &models.ProxyHost{
		UUID:        "uuid-2",
		DomainNames: "test.example.com",
		ForwardHost: "127.0.0.1",
		ForwardPort: 8081,
	}
	err = service.Create(dup)
	assert.Error(t, err)

	// GetByID
	fetched, err := service.GetByID(host.ID)
	assert.NoError(t, err)
	assert.Equal(t, host.DomainNames, fetched.DomainNames)

	// GetByUUID
	fetchedUUID, err := service.GetByUUID(host.UUID)
	assert.NoError(t, err)
	assert.Equal(t, host.ID, fetchedUUID.ID)

	// Update
	host.ForwardPort = 9090
	err = service.Update(host)
	assert.NoError(t, err)

	fetched, err = service.GetByID(host.ID)
	assert.NoError(t, err)
	assert.Equal(t, 9090, fetched.ForwardPort)

	// Update Duplicate
	host2 := &models.ProxyHost{
		UUID:        "uuid-3",
		DomainNames: "other.example.com",
		ForwardHost: "127.0.0.1",
		ForwardPort: 8080,
	}
	service.Create(host2)

	host.DomainNames = "other.example.com" // Conflict with host2
	err = service.Update(host)
	assert.Error(t, err)

	// List
	hosts, err := service.List()
	assert.NoError(t, err)
	assert.Len(t, hosts, 2)

	// Delete
	err = service.Delete(host.ID)
	assert.NoError(t, err)

	_, err = service.GetByID(host.ID)
	assert.Error(t, err)
}

func TestProxyHostService_TestConnection(t *testing.T) {
	db := setupProxyHostTestDB(t)
	service := NewProxyHostService(db)

	// 1. Invalid Input
	err := service.TestConnection("", 80)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid host or port")

	err = service.TestConnection("example.com", 0)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid host or port")

	// 2. Connection Failure (Unreachable)
	err = service.TestConnection("localhost", 54321)
	assert.Error(t, err)

	// 3. Connection Success
	// Start a local listener
	l, err := net.Listen("tcp", "127.0.0.1:0")
	require.NoError(t, err)
	defer l.Close()
	addr := l.Addr().(*net.TCPAddr)

	err = service.TestConnection(addr.IP.String(), addr.Port)
	assert.NoError(t, err)
}

// TestProxyHostService_AdvancedConfig tests advanced config JSON normalization
func TestProxyHostService_AdvancedConfig(t *testing.T) {
	db := setupProxyHostTestDB(t)
	service := NewProxyHostService(db)

	tests := []struct {
		name           string
		advancedConfig string
		wantErr        bool
	}{
		{
			name:           "Empty advanced config",
			advancedConfig: "",
			wantErr:        false,
		},
		{
			name:           "Valid JSON object",
			advancedConfig: `{"key": "value"}`,
			wantErr:        false,
		},
		{
			name:           "Valid JSON array",
			advancedConfig: `[{"directive": "test"}]`,
			wantErr:        false,
		},
		{
			name:           "Invalid JSON",
			advancedConfig: `{invalid json}`,
			wantErr:        true,
		},
		{
			name:           "Valid nested config",
			advancedConfig: `{"nested": {"key": "value"}}`,
			wantErr:        false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			host := &models.ProxyHost{
				UUID:           fmt.Sprintf("uuid-%s", tt.name),
				DomainNames:    fmt.Sprintf("test-%s.example.com", tt.name),
				ForwardHost:    "127.0.0.1",
				ForwardPort:    8080,
				AdvancedConfig: tt.advancedConfig,
			}

			err := service.Create(host)
			if tt.wantErr {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), "invalid advanced_config")
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

// TestProxyHostService_UpdateAdvancedConfig tests updating with advanced config
func TestProxyHostService_UpdateAdvancedConfig(t *testing.T) {
	db := setupProxyHostTestDB(t)
	service := NewProxyHostService(db)

	// Create host without advanced config
	host := &models.ProxyHost{
		UUID:        "uuid-update",
		DomainNames: "update.example.com",
		ForwardHost: "127.0.0.1",
		ForwardPort: 8080,
	}
	require.NoError(t, service.Create(host))

	// Update with valid advanced config
	host.AdvancedConfig = `{"custom": "directive"}`
	err := service.Update(host)
	assert.NoError(t, err)

	fetched, err := service.GetByID(host.ID)
	require.NoError(t, err)
	assert.Contains(t, fetched.AdvancedConfig, "custom")

	// Update with invalid advanced config
	host.AdvancedConfig = `{invalid}`
	err = service.Update(host)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid advanced_config")
}

// TestProxyHostService_EmptyDomain tests validation with empty domain
func TestProxyHostService_EmptyDomain(t *testing.T) {
	db := setupProxyHostTestDB(t)
	service := NewProxyHostService(db)

	// Validate empty domain (should work as no conflict)
	err := service.ValidateUniqueDomain("", 0)
	assert.NoError(t, err)
}
