package installer

import (
	_ "embed"
	"fmt"
	"net"
	"testing"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/errors"
)

var (
	env = EnvConfig{
		InfraName:       "dodo",
		Id:              "id",
		ContactEmail:    "foo@bar.ge",
		Domain:          "bar.ge",
		PrivateDomain:   "p.bar.ge",
		PublicIP:        []net.IP{net.ParseIP("1.2.3.4")},
		NameserverIP:    []net.IP{net.ParseIP("1.2.3.4")},
		NamespacePrefix: "id-",
		Network: EnvNetwork{
			DNS:            net.ParseIP("1.1.1.1"),
			DNSInClusterIP: net.ParseIP("2.2.2.2"),
			Ingress:        net.ParseIP("3.3.3.3"),
			Headscale:      net.ParseIP("4.4.4.4"),
			ServicesFrom:   net.ParseIP("5.5.5.5"),
			ServicesTo:     net.ParseIP("6.6.6.6"),
		},
	}

	infraNetworks = []InfraNetwork{
		{
			Name:               "Public",
			IngressClass:       fmt.Sprintf("%s-ingress-public", env.InfraName),
			CertificateIssuer:  fmt.Sprintf("%s-public", env.Id),
			AllocatePortAddr:   fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/allocate", env.InfraName),
			ReservePortAddr:    fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/reserve", env.InfraName),
			DeallocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/remove", env.InfraName),
		},
	}

	networks = []Network{
		{
			Name:               "Public",
			IngressClass:       fmt.Sprintf("%s-ingress-public", env.InfraName),
			CertificateIssuer:  fmt.Sprintf("%s-public", env.Id),
			Domain:             env.Domain,
			AllocatePortAddr:   fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/allocate", env.InfraName),
			ReservePortAddr:    fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/reserve", env.InfraName),
			DeallocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/remove", env.InfraName),
		},
		{
			Name:               "Private",
			IngressClass:       fmt.Sprintf("%s-ingress-private", env.Id),
			Domain:             env.PrivateDomain,
			AllocatePortAddr:   fmt.Sprintf("http://port-allocator.%s-ingress-private.svc.cluster.local/api/allocate", env.Id),
			ReservePortAddr:    fmt.Sprintf("http://port-allocator.%s-ingress-private.svc.cluster.local/api/reserve", env.Id),
			DeallocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-private.svc.cluster.local/api/remove", env.Id),
		},
	}

	clusters = []Cluster{
		{
			Name: "default",
		},
		{
			Name:             "io",
			IngressClassName: "io",
			Kubeconfig:       "kubeconfig",
		},
	}
)

func TestAuthProxyEnabled(t *testing.T) {
	r := NewInMemoryAppRepository(CreateAllApps())
	for _, app := range []string{"rpuppy"} {
		a, err := FindEnvApp(r, app)
		if err != nil {
			t.Fatal(err)
		}
		if a == nil {
			t.Fatal("returned app is nil")
		}
		release := Release{
			Namespace: "foo",
		}
		values := map[string]any{
			"network":   "Public",
			"subdomain": "woof",
			"auth": map[string]any{
				"enabled": true,
				"groups":  "a,b",
			},
		}
		rendered, err := a.Render(release, env, networks, nil, values, nil, nil)
		if err != nil {
			t.Fatal(err)
		}
		for _, r := range rendered.Resources {
			t.Log(string(r))
		}
	}
}

func TestAuthProxyDisabled(t *testing.T) {
	r := NewInMemoryAppRepository(CreateAllApps())
	for _, app := range []string{"rpuppy"} {
		a, err := FindEnvApp(r, app)
		if err != nil {
			t.Fatal(err)
		}
		if a == nil {
			t.Fatal("returned app is nil")
		}
		release := Release{
			Namespace: "foo",
		}
		values := map[string]any{
			"network":   "Public",
			"subdomain": "woof",
			"auth": map[string]any{
				"enabled": false,
			},
		}
		rendered, err := a.Render(release, env, networks, nil, values, nil, nil)
		if err != nil {
			t.Fatal(err)
		}
		for _, r := range rendered.Resources {
			t.Log(string(r))
		}
	}
}

func TestGroupMemberships(t *testing.T) {
	r := NewInMemoryAppRepository(CreateAllApps())
	a, err := FindEnvApp(r, "memberships")
	if err != nil {
		t.Fatal(err)
	}
	if a == nil {
		t.Fatal("returned app is nil")
	}
	release := Release{
		Namespace: "foo",
	}
	values := map[string]any{
		"network":    "Public",
		"authGroups": "foo,bar",
	}
	rendered, err := a.Render(release, env, networks, nil, values, nil, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
}

func TestGerrit(t *testing.T) {
	r := NewInMemoryAppRepository(CreateAllApps())
	a, err := FindEnvApp(r, "gerrit")
	if err != nil {
		t.Fatal(err)
	}
	if a == nil {
		t.Fatal("returned app is nil")
	}
	release := Release{
		Namespace: "foo",
	}
	values := map[string]any{
		"subdomain": "gerrit",
		"network":   "Private",
		"key": map[string]any{
			"public":  "foo",
			"private": "bar",
		},
		"sshPort": 22,
	}
	rendered, err := a.Render(release, env, networks, nil, values, nil, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
}

func TestJenkins(t *testing.T) {
	r := NewInMemoryAppRepository(CreateAllApps())
	a, err := FindEnvApp(r, "jenkins")
	if err != nil {
		t.Fatal(err)
	}
	if a == nil {
		t.Fatal("returned app is nil")
	}
	release := Release{
		Namespace: "foo",
	}
	values := map[string]any{
		"subdomain": "jenkins",
		"network":   "Private",
	}
	rendered, err := a.Render(release, env, networks, nil, values, nil, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
}

func TestIngressPublic(t *testing.T) {
	r := NewInMemoryAppRepository(CreateAllApps())
	a, err := FindInfraApp(r, "ingress-public")
	if err != nil {
		t.Fatal(err)
	}
	if a == nil {
		t.Fatal("returned app is nil")
	}
	release := Release{
		Namespace: "foo",
	}
	infra := InfraConfig{
		Name:                 "dodo",
		PublicIP:             []net.IP{net.ParseIP("1.2.3.4")},
		InfraNamespacePrefix: "id-",
		InfraAdminPublicKey:  []byte("foo"),
	}
	values := map[string]any{
		"sshPrivateKey": "private",
	}
	rendered, err := a.Render(release, infra, infraNetworks, values, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
}

func TestPrivateNetwork(t *testing.T) {
	r := NewInMemoryAppRepository(CreateAllApps())
	a, err := FindEnvApp(r, "private-network")
	if err != nil {
		t.Fatal(err)
	}
	if a == nil {
		t.Fatal("returned app is nil")
	}
	release := Release{
		Namespace: "foo",
	}
	values := map[string]any{
		"privateNetwork": map[string]any{
			"hostname": "foo",
			"username": "bar",
			"ipSubnet": "123123",
		},
		"sshPrivateKey": "private",
	}
	rendered, err := a.Render(release, env, networks, nil, values, nil, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
}

func TestAppPackages(t *testing.T) {
	contents, err := valuesTmpls.ReadFile("values-tmpl/rpuppy.cue")
	if err != nil {
		t.Fatal(err)
	}
	app, err := NewCueEnvApp(CueAppData{
		"base.cue":   []byte(cueBaseConfig),
		"app.cue":    []byte(contents),
		"global.cue": []byte(cueEnvAppGlobal),
	})
	if err != nil {
		t.Fatal(err)
	}
	release := Release{
		Namespace: "foo",
	}
	values := map[string]any{
		"network":   "Public",
		"subdomain": "woof",
		"auth": map[string]any{
			"enabled": true,
			"groups":  "a,b",
		},
	}
	rendered, err := app.Render(release, env, networks, nil, values, nil, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
	for _, r := range rendered.Data {
		t.Log(string(r))
	}
}

func TestAppPackagesRemoteCluster(t *testing.T) {
	contents, err := valuesTmpls.ReadFile("values-tmpl/rpuppy.cue")
	if err != nil {
		t.Fatal(err)
	}
	app, err := NewCueEnvApp(CueAppData{
		"base.cue":   []byte(cueBaseConfig),
		"app.cue":    []byte(contents),
		"global.cue": []byte(cueEnvAppGlobal),
	})
	if err != nil {
		t.Fatal(err)
	}
	release := Release{
		Namespace: "foo",
	}
	values := map[string]any{
		"network":   "Public",
		"subdomain": "woof",
		"auth": map[string]any{
			"enabled": false,
		},
		"cluster": "io",
	}
	rendered, err := app.Render(release, env, networks, clusters, values, nil, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
	for _, r := range rendered.Data {
		t.Log(string(r))
	}
}

func TestLonghorn(t *testing.T) {
	contents, err := valuesTmpls.ReadFile("values-tmpl/longhorn.cue")
	if err != nil {
		t.Fatal(err)
	}
	app, err := NewCueEnvApp(CueAppData{
		"base.cue":   []byte(cueBaseConfig),
		"app.cue":    []byte(contents),
		"global.cue": []byte(cueEnvAppGlobal),
	})
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("%+v\n", app.Schema())
}

func TestDNSGateway(t *testing.T) {
	contents, err := valuesTmpls.ReadFile("values-tmpl/dns-gateway.cue")
	if err != nil {
		t.Fatal(err)
	}
	app, err := NewCueInfraApp(CueAppData{
		"base.cue":   []byte(cueBaseConfig),
		"app.cue":    []byte(contents),
		"global.cue": []byte(cueInfraAppGlobal),
	})
	if err != nil {
		t.Fatal(err)
	}
	release := Release{
		Namespace:     "foo",
		AppInstanceId: "dns-gateway",
		RepoAddr:      "ssh://192.168.100.210:22/config",
		AppDir:        "/infrastructure/gns-gateway",
	}
	infra := InfraConfig{
		Name:                 "dodo",
		PublicIP:             []net.IP{net.ParseIP("135.181.48.180"), net.ParseIP("65.108.39.172")},
		InfraNamespacePrefix: "dodo-",
		InfraAdminPublicKey:  []byte("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/ZRj0QJ0j+3udh0ANN9mJyEzrATZIOAHfNikDMpSHqrVbPZqpeHGbdYrSksCvXPXfissIZoYU4CCXX007jY0W6e1mPf1nObYh2eUT1dHo/8UtGaf9nYk+kEGU/k3utN4Uzkxa13IFh9pYERX+o0Ad3X5wh0vi5hjOBAJVKOCD9d3aipeR9piUb+qrkFDXf9fozMFn7D9nALkpJBVuGxwl/76f8K6hRxBEmPqZwIMfklzX15nRdLEcsGFJpYLYXsonbr1P3moMJFBBbQFv6M6JO9rrwA+swXpWMoScI7m/nziSEPLAb+ziv+/OyhqzeC9CQner73V0m8+2DmtcgTuSe1qHRtOScPyIjBfxoXaUx1IUkgq1NXt8k+EBO2mxnVpKdyDCvwT1Tb7088P8f8cSLtUOmUdEiAhB8bfQFprzm2KrlufenfhMvdvQPU4VfWlkQ4smLYt2yVaaXoxZMy5yD3X6LFurNXwee/Gn6di+DWqsASAOsmpsNgSCGhT8wxM= lekva@gl-mbp-m1-max.local"),
	}
	values := map[string]any{
		"servers": []EnvDNS{EnvDNS{"v1.dodo.cloud", "10.0.1.2"}},
	}
	rendered, err := app.Render(release, infra, infraNetworks, values, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
	for _, r := range rendered.Data {
		t.Log(string(r))
	}
}

var dodoAppRemoteClusterCue = `
service: [{
    name: "app"
	type: "golang:1.22.0"
	run: "main.go"
	ingress: [{
		network: "private"
		subdomain: "testapp"
		auth: enabled: false
	}]
	dev: {
		enabled: false
	}
    cluster: "io"
    source: repository: "ssh://foo.bar"
}]`

func TestDodoAppRemoteCluster(t *testing.T) {
	app, err := NewDodoApp([]byte(dodoAppRemoteClusterCue))
	if err != nil {
		for _, e := range errors.Errors(err) {
			t.Log(e)
		}
		t.Fatal(err)
	}

	release := Release{
		Namespace:     "foo",
		AppInstanceId: "foo-bar",
		RepoAddr:      "ssh://192.168.100.210:22/config",
		AppDir:        "/foo/bar",
	}
	keyGen := testKeyGen{}
	r, err := app.Render(release, env, networks, clusters, map[string]any{
		"managerAddr":   "3",
		"appId":         "4",
		"sshPrivateKey": "6",
	}, nil, keyGen)
	if err != nil {
		for _, e := range errors.Errors(err) {
			for _, f := range errors.Errors(e) {
				for _, g := range errors.Errors(f) {
					t.Log(g)
				}
			}
		}
		t.Fatal(err)
	}
	t.Log(string(r.Raw))
}

var dodoAppDevDisabledCue = `
service: [{
    name: "app"
	type: "golang:1.22.0"
	run: "main.go"
	ingress: [{
		network: "private"
		subdomain: "testapp"
		auth: enabled: false
	}]
	dev: {
		enabled: false
	}
    source: repository: "ssh://foo.bar"
}]

mongodb: [{
  name: "db"
  size: "10Gi"
}]

postgresql: [{
  name: "pg"
  size: "10Gi"
}]
`

var dodoAppDevEnabledCue = `
service: [{
    name: "app"
	type: "golang:1.20.0"
	run: "main.go"
	ingress: [{
		network: "private"
		subdomain: "testapp"
		auth: enabled: false
	}]
	volume: ["data"]
	dev: {
		enabled: true
		mode: "VM"
		username: "gio"
	}
    source: repository: "ssh://foo.bar"
}]

volume: [{
  name: "data"
  size: "5Gi"
}]

mongodb: [{
  name: "db"
  size: "10Gi"
}]
`

func TestDodoAppDevDisabled(t *testing.T) {
	app, err := NewDodoApp([]byte(dodoAppDevDisabledCue))
	if err != nil {
		for _, e := range errors.Errors(err) {
			t.Log(e)
		}
		t.Fatal(err)
	}

	release := Release{
		Namespace:     "foo",
		AppInstanceId: "foo-bar",
		RepoAddr:      "ssh://192.168.100.210:22/config",
		AppDir:        "/foo/bar",
	}
	keyGen := testKeyGen{}
	r, err := app.Render(release, env, networks, nil, map[string]any{
		"managerAddr":   "",
		"appId":         "",
		"sshPrivateKey": "",
	}, nil, keyGen)
	if err != nil {
		for _, e := range errors.Errors(err) {
			for _, f := range errors.Errors(e) {
				for _, g := range errors.Errors(f) {
					t.Log(g)
				}
			}
		}
		t.Fatal(err)
	}
	t.Log(string(r.Raw))
}

func TestDodoAppDevEnabled(t *testing.T) {
	app, err := NewDodoApp([]byte(dodoAppDevEnabledCue))
	if err != nil {
		for _, e := range errors.Errors(err) {
			t.Log(e)
		}
		t.Fatal(err)
	}

	release := Release{
		Namespace:     "foo",
		AppInstanceId: "foo-bar",
		RepoAddr:      "ssh://192.168.100.210:22/config",
		AppDir:        "/foo/bar",
	}
	keyGen := testKeyGen{}
	r, err := app.Render(release, env, networks, nil, map[string]any{
		"managerAddr":         "",
		"appId":               "",
		"sshPrivateKey":       "",
		"username":            "",
		"password_mongodb_db": "foo",
	}, nil, keyGen)
	if err != nil {
		for _, e := range errors.Errors(err) {
			t.Log(e)
		}
		t.Fatal(err)
	}
	t.Log(string(r.Raw))
}

func TestDodoAppInstance(t *testing.T) {
	r := NewInMemoryAppRepository(CreateAllApps())
	a, err := FindEnvApp(r, "dodo-app-instance")
	if err != nil {
		t.Fatal(err)
	}
	if a == nil {
		t.Fatal("returned app is nil")
	}
	release := Release{
		Namespace: "foo",
	}
	values := map[string]any{
		"repoAddr":         "",
		"repoHost":         "",
		"branch":           "",
		"gitRepoPublicKey": "",
		"username":         "",
	}
	rendered, err := a.Render(release, env, networks, nil, values, nil, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
}

func TestDodoApp(t *testing.T) {
	contents, err := valuesTmpls.ReadFile("values-tmpl/dodo-app.cue")
	if err != nil {
		t.Fatal(err)
	}
	app, err := NewCueEnvApp(CueAppData{
		"base.cue":   []byte(cueBaseConfig),
		"app.cue":    []byte(contents),
		"global.cue": []byte(cueEnvAppGlobal),
	})
	if err != nil {
		t.Fatal(err)
	}
	t.Log(app.Schema())
}

func TestAppVirtualMachine(t *testing.T) {
	contents, err := valuesTmpls.ReadFile("values-tmpl/virtual-machine.cue")
	if err != nil {
		t.Fatal(err)
	}
	app, err := NewCueEnvApp(CueAppData{
		"base.cue":   []byte(cueBaseConfig),
		"app.cue":    []byte(contents),
		"global.cue": []byte(cueEnvAppGlobal),
	})
	if err != nil {
		t.Fatal(err)
	}
	release := Release{
		Namespace: "foo",
	}
	values := map[string]any{
		"name":     "foo",
		"username": "bar",
		"cpuCores": 1,
		"memory":   "1Gi",
	}
	rendered, err := app.Render(release, env, networks, nil, values, nil, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rendered.Resources {
		t.Log(string(r))
	}
	for _, r := range rendered.Data {
		t.Log(string(r))
	}
}

func TestUpdateUsesPreviousValues(t *testing.T) {
	contents := `
input: {
  port: int | *5
}

out: {
  openPort: [{
    name: "api"
    network: networks["private"]
    port: input.port
    service: {
      name: "app"
      port: 8080
    }
  }]
}

input: {
  port: 10
}
`
	app, err := NewCueEnvApp(CueAppData{
		"base.cue":   []byte(cueBaseConfig),
		"app.cue":    []byte(contents),
		"global.cue": []byte(cueEnvAppGlobal),
	})
	if err != nil {
		t.Fatal(err)
	}
	a, ok := app.(cueEnvApp)
	if !ok {
		t.Fatal("expected cue app")
	}
	defaults, err := ExtractDefaultValues(a.cfg.LookupPath(cue.ParsePath("input")))
	if err != nil {
		t.Fatal(err)
	}
	t.Log(defaults)
	if defaults.(map[string]any)["port"].(int64) != 10 {
		t.Fatal("port")
	}
}

func TestMerge(t *testing.T) {
	x := map[string]any{
		"key": map[string]any{
			"pub":  1,
			"priv": 2,
		},
	}
	y := map[string]any{
		"foo": 3,
	}
	z := merge(x, y)
	if _, ok := z["foo"]; !ok {
		t.Fatal(z)
	}
}
