// Copyright IBM Corp. 2020, 2026
// SPDX-License-Identifier: BUSL-1.1

package oss_test

import (
	"context"
	"testing"

	"github.com/hashicorp/boundary/internal/db"
	"github.com/hashicorp/boundary/internal/db/common"
	"github.com/hashicorp/boundary/internal/db/schema"
	"github.com/hashicorp/boundary/testing/dbtest"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestMigrations_WareHouse_HostAddresses(t *testing.T) {
	const (
		priorMigration   = 25001
		currentMigration = 26001
	)

	t.Parallel()
	ctx := context.Background()
	dialect := dbtest.Postgres

	c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1))
	require.NoError(t, err)
	t.Cleanup(func() {
		require.NoError(t, c())
	})
	d, err := common.SqlOpen(dialect, u)
	require.NoError(t, err)

	// migration to the prior migration (before the one we want to test)
	m, err := schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
		schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": priorMigration}),
	))
	require.NoError(t, err)

	_, err = m.ApplyMigrations(ctx)
	require.NoError(t, err)
	state, err := m.CurrentState(ctx)
	require.NoError(t, err)
	want := &schema.State{
		Initialized: true,
		Editions: []schema.EditionState{
			{
				Name:                  "oss",
				BinarySchemaVersion:   priorMigration,
				DatabaseSchemaVersion: priorMigration,
				DatabaseSchemaState:   schema.Equal,
			},
		},
	}
	require.Equal(t, want, state)

	// get a connection
	dbType, err := db.StringToDbType(dialect)
	require.NoError(t, err)
	conn, err := db.Open(ctx, dbType, u)
	require.NoError(t, err)
	rw := db.New(conn)

	type whAddress struct {
		Address                   string
		AddressType               string
		IpAddressFamily           string
		PrivateIpAddressIndicator string
		DnsName                   string
		Ip4Address                string
		Ip6Address                string
	}
	addresses := []whAddress{
		{
			Address:                   "10.0.0.1",
			AddressType:               "IP Address",
			IpAddressFamily:           "IPv4",
			PrivateIpAddressIndicator: "Private IP address",
			DnsName:                   "Not Applicable",
			Ip4Address:                "10.0.0.1",
			Ip6Address:                "Not Applicable",
		},
		{
			Address:                   "12.3.4.5",
			AddressType:               "IP Address",
			IpAddressFamily:           "IPv4",
			PrivateIpAddressIndicator: "Public IP address",
			DnsName:                   "Not Applicable",
			Ip4Address:                "12.3.4.5",
			Ip6Address:                "Not Applicable",
		},
		{
			Address:                   "fe80::1234:5678:1234:5678",
			AddressType:               "IP Address",
			IpAddressFamily:           "IPv6",
			PrivateIpAddressIndicator: "Private IP address",
			DnsName:                   "Not Applicable",
			Ip4Address:                "Not Applicable",
			Ip6Address:                "fe80::1234:5678:1234:5678",
		},
		{
			Address:                   "2001:4860:4860::8888",
			AddressType:               "IP Address",
			IpAddressFamily:           "IPv6",
			PrivateIpAddressIndicator: "Public IP address",
			DnsName:                   "Not Applicable",
			Ip4Address:                "Not Applicable",
			Ip6Address:                "2001:4860:4860::8888",
		},
		{
			Address:                   "foo",
			AddressType:               "DNS Name",
			IpAddressFamily:           "Not Applicable",
			PrivateIpAddressIndicator: "Not Applicable",
			DnsName:                   "foo",
			Ip4Address:                "Not Applicable",
			Ip6Address:                "Not Applicable",
		},
		{
			Address:                   "something.com",
			AddressType:               "DNS Name",
			IpAddressFamily:           "Not Applicable",
			PrivateIpAddressIndicator: "Not Applicable",
			DnsName:                   "something.com",
			Ip4Address:                "Not Applicable",
			Ip6Address:                "Not Applicable",
		},
		{
			Address:                   "10.0.foo.com",
			AddressType:               "DNS Name",
			IpAddressFamily:           "Not Applicable",
			PrivateIpAddressIndicator: "Not Applicable",
			DnsName:                   "10.0.foo.com",
			Ip4Address:                "Not Applicable",
			Ip6Address:                "Not Applicable",
		},
		{
			Address:                   "not valid anything",
			AddressType:               "DNS Name",
			IpAddressFamily:           "Not Applicable",
			PrivateIpAddressIndicator: "Not Applicable",
			DnsName:                   "not valid anything",
			Ip4Address:                "Not Applicable",
			Ip6Address:                "Not Applicable",
		},
		{
			Address:                   "Unsupported",
			AddressType:               "Unknown",
			IpAddressFamily:           "Not Applicable",
			PrivateIpAddressIndicator: "Not Applicable",
			DnsName:                   "Not Applicable",
			Ip4Address:                "Not Applicable",
			Ip6Address:                "Not Applicable",
		},
		{
			Address:                   "Unknown",
			AddressType:               "Unknown",
			IpAddressFamily:           "Not Applicable",
			PrivateIpAddressIndicator: "Not Applicable",
			DnsName:                   "Not Applicable",
			Ip4Address:                "Not Applicable",
			Ip6Address:                "Not Applicable",
		},
	}
	{
		q := `
insert into wh_host_dimension
(host_id, host_type, host_name, host_description, host_address,
host_set_id, host_set_type, host_set_name, host_set_description,
host_catalog_id, host_catalog_type, host_catalog_name, host_catalog_description,
target_id, target_type, target_name, target_description, target_default_port_number,
target_session_max_seconds, target_session_connection_limit,
project_id, project_name, project_description,
organization_id, organization_name, organization_description,
current_row_indicator, row_effective_time, row_expiration_time)
values
('h_1234567890', 'static', 'None', 'None', ?,
'hs_1234567890', 'static', 'None', 'None',
'hc_1234567890', 'static', 'None', 'None',
't_1234567890', 'tcp', 'None', 'None', 0,
30, 1,
'p_1234567890', 'None', 'None',
'o_1234567890', 'None', 'None',
'Expired', current_timestamp, current_timestamp)
`
		for _, a := range addresses {
			_, err := rw.Exec(ctx, q, []any{a.Address})
			require.NoError(t, err)
		}
		// Duplicate a few records...
		_, err := rw.Exec(ctx, q, []any{addresses[1].Address})
		require.NoError(t, err)
		_, err = rw.Exec(ctx, q, []any{addresses[5].Address})
		require.NoError(t, err)
	}

	// now we're ready for the migration we want to test.
	m, err = schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
		schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": currentMigration}),
	))
	require.NoError(t, err)

	_, err = m.ApplyMigrations(ctx)
	require.NoError(t, err)
	state, err = m.CurrentState(ctx)
	require.NoError(t, err)
	want = &schema.State{
		Initialized: true,
		Editions: []schema.EditionState{
			{
				Name:                  "oss",
				BinarySchemaVersion:   currentMigration,
				DatabaseSchemaVersion: currentMigration,
				DatabaseSchemaState:   schema.Equal,
			},
		},
	}
	require.Equal(t, want, state)
	// Now read all the converted rows and see if we have transformed and
	// calculated the values as expected.
	{
		rows, err := rw.Query(ctx, "select * from wh_network_address_dimension", nil)
		require.NoError(t, err)
		var results []whAddress
		for rows.Next() {
			var addr whAddress
			require.NoError(t, rw.ScanRows(context.Background(), rows, &addr))
			results = append(results, addr)
		}
		assert.NoError(t, rows.Err())
		assert.ElementsMatch(t, results, addresses)
	}
}
