package integration

import (
	"encoding/json"
	"fmt"
	"testing"
	"time"

	v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
	"github.com/juanfont/headscale/integration/hsic"
	"github.com/juanfont/headscale/integration/tsic"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRouteCommand(t *testing.T) {
	IntegrationSkip(t)

	spec := ScenarioSpec{
		Users:        []string{"route-user"},
		NodesPerUser: 1,
	}

	scenario, err := NewScenario(spec)
	assertNoErr(t, err)
	defer scenario.ShutdownAssertNoPanics(t)

	err = scenario.CreateHeadscaleEnv(
		[]tsic.Option{tsic.WithAcceptRoutes()},
		hsic.WithTestName("cliroutes"),
	)
	assertNoErr(t, err)

	headscale, err := scenario.Headscale()
	assertNoErr(t, err)

	// Wait for setup to complete
	err = scenario.WaitForTailscaleSync()
	assertNoErr(t, err)

	// Wait for node to be registered
	assert.EventuallyWithT(t, func(c *assert.CollectT) {
		var listNodes []*v1.Node
		err := executeAndUnmarshal(headscale,
			[]string{
				"headscale",
				"nodes",
				"list",
				"--output",
				"json",
			},
			&listNodes,
		)
		assert.NoError(c, err)
		assert.Len(c, listNodes, 1)
	}, 30*time.Second, 1*time.Second)

	// Get the node ID for route operations
	var listNodes []*v1.Node
	err = executeAndUnmarshal(headscale,
		[]string{
			"headscale",
			"nodes",
			"list",
			"--output",
			"json",
		},
		&listNodes,
	)
	assertNoErr(t, err)
	require.Len(t, listNodes, 1)
	nodeID := listNodes[0].GetId()

	t.Run("test_route_advertisement", func(t *testing.T) {
		// Get the first tailscale client
		allClients, err := scenario.ListTailscaleClients()
		assertNoErr(t, err)
		require.NotEmpty(t, allClients, "should have at least one client")
		client := allClients[0]

		// Advertise a route
		_, _, err = client.Execute([]string{
			"tailscale",
			"set",
			"--advertise-routes=10.0.0.0/24",
		})
		assertNoErr(t, err)

		// Wait for route to appear in Headscale
		assert.EventuallyWithT(t, func(c *assert.CollectT) {
			var updatedNodes []*v1.Node
			err := executeAndUnmarshal(headscale,
				[]string{
					"headscale",
					"nodes",
					"list",
					"--output",
					"json",
				},
				&updatedNodes,
			)
			assert.NoError(c, err)
			assert.Len(c, updatedNodes, 1)
			assert.Greater(c, len(updatedNodes[0].GetAvailableRoutes()), 0, "node should have available routes")
		}, 30*time.Second, 1*time.Second)
	})

	t.Run("test_route_approval", func(t *testing.T) {
		// List available routes
		_, err := headscale.Execute(
			[]string{
				"headscale",
				"nodes",
				"list-routes",
				"--node",
				fmt.Sprintf("%d", nodeID),
			},
		)
		assertNoErr(t, err)

		// Approve a route
		_, err = headscale.Execute(
			[]string{
				"headscale",
				"nodes",
				"approve-routes",
				"--node",
				fmt.Sprintf("%d", nodeID),
				"--routes",
				"10.0.0.0/24",
			},
		)
		assertNoErr(t, err)

		// Verify route is approved
		assert.EventuallyWithT(t, func(c *assert.CollectT) {
			var updatedNodes []*v1.Node
			err := executeAndUnmarshal(headscale,
				[]string{
					"headscale",
					"nodes",
					"list",
					"--output",
					"json",
				},
				&updatedNodes,
			)
			assert.NoError(c, err)
			assert.Len(c, updatedNodes, 1)
			assert.Contains(c, updatedNodes[0].GetApprovedRoutes(), "10.0.0.0/24", "route should be approved")
		}, 30*time.Second, 1*time.Second)
	})

	t.Run("test_route_removal", func(t *testing.T) {
		// Remove approved routes
		_, err := headscale.Execute(
			[]string{
				"headscale",
				"nodes",
				"approve-routes",
				"--node",
				fmt.Sprintf("%d", nodeID),
				"--routes",
				"", // Empty string removes all routes
			},
		)
		assertNoErr(t, err)

		// Verify routes are removed
		assert.EventuallyWithT(t, func(c *assert.CollectT) {
			var updatedNodes []*v1.Node
			err := executeAndUnmarshal(headscale,
				[]string{
					"headscale",
					"nodes",
					"list",
					"--output",
					"json",
				},
				&updatedNodes,
			)
			assert.NoError(c, err)
			assert.Len(c, updatedNodes, 1)
			assert.Empty(c, updatedNodes[0].GetApprovedRoutes(), "approved routes should be empty")
		}, 30*time.Second, 1*time.Second)
	})

	t.Run("test_route_json_output", func(t *testing.T) {
		// Test JSON output for route commands
		result, err := headscale.Execute(
			[]string{
				"headscale",
				"nodes",
				"list-routes",
				"--node",
				fmt.Sprintf("%d", nodeID),
				"--output",
				"json",
			},
		)
		assertNoErr(t, err)

		// Verify JSON output is valid
		var routes interface{}
		err = json.Unmarshal([]byte(result), &routes)
		assert.NoError(t, err, "route command should produce valid JSON output")
	})
}

func TestRouteCommandEdgeCases(t *testing.T) {
	IntegrationSkip(t)

	spec := ScenarioSpec{
		Users: []string{"route-test-user"},
	}

	scenario, err := NewScenario(spec)
	assertNoErr(t, err)
	defer scenario.ShutdownAssertNoPanics(t)

	err = scenario.CreateHeadscaleEnv([]tsic.Option{}, hsic.WithTestName("cliroutesedge"))
	assertNoErr(t, err)

	headscale, err := scenario.Headscale()
	assertNoErr(t, err)

	t.Run("test_route_commands_with_invalid_node", func(t *testing.T) {
		// Test route commands with non-existent node ID
		_, err := headscale.Execute(
			[]string{
				"headscale",
				"nodes",
				"list-routes",
				"--node",
				"999999",
			},
		)
		// Should handle error gracefully
		assert.Error(t, err, "should fail for non-existent node")
	})

	t.Run("test_route_approval_invalid_routes", func(t *testing.T) {
		// Test route approval with invalid CIDR
		_, err := headscale.Execute(
			[]string{
				"headscale",
				"nodes",
				"approve-routes",
				"--node",
				"1",
				"--routes",
				"invalid-cidr",
			},
		)
		// Should handle invalid CIDR gracefully
		assert.Error(t, err, "should fail for invalid CIDR")
	})
}

func TestRouteCommandHelp(t *testing.T) {
	IntegrationSkip(t)

	spec := ScenarioSpec{
		Users: []string{"help-user"},
	}

	scenario, err := NewScenario(spec)
	assertNoErr(t, err)
	defer scenario.ShutdownAssertNoPanics(t)

	err = scenario.CreateHeadscaleEnv([]tsic.Option{}, hsic.WithTestName("cliroutehelp"))
	assertNoErr(t, err)

	headscale, err := scenario.Headscale()
	assertNoErr(t, err)

	t.Run("test_list_routes_help", func(t *testing.T) {
		result, err := headscale.Execute(
			[]string{
				"headscale",
				"nodes",
				"list-routes",
				"--help",
			},
		)
		assertNoErr(t, err)

		// Verify help text contains expected information
		assert.Contains(t, result, "list-routes", "help should mention list-routes command")
		assert.Contains(t, result, "node", "help should mention node flag")
	})

	t.Run("test_approve_routes_help", func(t *testing.T) {
		result, err := headscale.Execute(
			[]string{
				"headscale",
				"nodes",
				"approve-routes",
				"--help",
			},
		)
		assertNoErr(t, err)

		// Verify help text contains expected information
		assert.Contains(t, result, "approve-routes", "help should mention approve-routes command")
		assert.Contains(t, result, "node", "help should mention node flag")
		assert.Contains(t, result, "routes", "help should mention routes flag")
	})
}
