package googlecompute

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"runtime"
	"testing"

	"github.com/hashicorp/packer/packer-plugin-sdk/communicator"
)

type MockTunnelDriver struct {
	StopTunnelCalled   bool
	StartTunnelCalled  bool
	StartTunnelTimeout int
}

func (m *MockTunnelDriver) StopTunnel() {
	m.StopTunnelCalled = true
}

func (m *MockTunnelDriver) StartTunnel(_ context.Context, _ string, timeout int) error {
	m.StartTunnelCalled = true
	m.StartTunnelTimeout = timeout
	return nil
}

func getTestStepStartTunnel() *StepStartTunnel {
	return &StepStartTunnel{
		IAPConf: &IAPConfig{
			IAP:              true,
			IAPLocalhostPort: 0,
			IAPHashBang:      "/bin/bash",
			IAPExt:           "",
		},
		CommConf: &communicator.Config{
			SSH: communicator.SSH{
				SSHPort: 1234,
			},
		},
		AccountFile: "/path/to/account_file.json",
		ProjectId:   "fake-project-123",
	}
}

func TestStepStartTunnel_CreateTempScript(t *testing.T) {
	s := getTestStepStartTunnel()

	args := []string{"compute", "start-iap-tunnel", "fakeinstance-12345",
		"1234", "--local-host-port=localhost:8774", "--zone", "us-central-b",
		"--project", "fake-project-123"}

	scriptPath, err := s.createTempGcloudScript(args)
	if err != nil {
		t.Fatalf("Shouldn't have error building script file.")
	}
	defer os.Remove(scriptPath)

	f, err := ioutil.ReadFile(scriptPath)
	if err != nil {
		t.Fatalf("couldn't read created inventoryfile: %s", err)
	}

	expected := `#!/bin/bash

gcloud auth activate-service-account --key-file='/path/to/account_file.json'
gcloud compute start-iap-tunnel fakeinstance-12345 1234 --local-host-port=localhost:8774 --zone us-central-b --project fake-project-123
`
	if runtime.GOOS == "windows" {
		// in real life you'd not be passing a HashBang here, but GIGO.
		expected = `#!/bin/bash

call gcloud auth activate-service-account --key-file "/path/to/account_file.json"
call gcloud compute start-iap-tunnel fakeinstance-12345 1234 --local-host-port=localhost:8774 --zone us-central-b --project fake-project-123
`
	}
	if fmt.Sprintf("%s", f) != expected {
		t.Fatalf("script didn't match expected:\n\n expected: \n%s\n; recieved: \n%s\n", expected, f)
	}
}

func TestStepStartTunnel_Cleanup(t *testing.T) {
	// Check IAP true
	s := getTestStepStartTunnel()
	td := &MockTunnelDriver{}
	s.tunnelDriver = td

	state := testState(t)
	s.Cleanup(state)

	if !td.StopTunnelCalled {
		t.Fatalf("Should have called StopTunnel, since IAP is true")
	}

	// Check IAP false
	s = getTestStepStartTunnel()
	td = &MockTunnelDriver{}
	s.tunnelDriver = td

	s.IAPConf.IAP = false

	s.Cleanup(state)

	if td.StopTunnelCalled {
		t.Fatalf("Should not have called StopTunnel, since IAP is false")
	}
}

func TestStepStartTunnel_ConfigurePort_port_set_by_user(t *testing.T) {
	s := getTestStepStartTunnel()
	s.IAPConf.IAPLocalhostPort = 8447

	ctx := context.TODO()
	err := s.ConfigureLocalHostPort(ctx)
	if err != nil {
		t.Fatalf("Shouldn't have error detecting port")
	}
	if s.IAPConf.IAPLocalhostPort != 8447 {
		t.Fatalf("Shouldn't have found new port; one was configured.")
	}
}

func TestStepStartTunnel_ConfigurePort_port_not_set_by_user(t *testing.T) {
	s := getTestStepStartTunnel()
	s.IAPConf.IAPLocalhostPort = 0

	ctx := context.TODO()
	err := s.ConfigureLocalHostPort(ctx)
	if err != nil {
		t.Fatalf("Shouldn't have error detecting port")
	}
	if s.IAPConf.IAPLocalhostPort == 0 {
		t.Fatalf("Should have found new port; none was configured.")
	}
}
