package audit_test

import (
	"database/sql"
	"reflect"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/stretchr/testify/require"

	"github.com/coder/coder/coderd/audit"
	"github.com/coder/coder/coderd/database"
)

func TestDiff(t *testing.T) {
	t.Parallel()

	runDiffTests(t, []diffTest[database.GitSSHKey]{
		{
			name: "Create",
			left: audit.Empty[database.GitSSHKey](),
			right: database.GitSSHKey{
				UserID:     uuid.UUID{1},
				CreatedAt:  time.Now(),
				UpdatedAt:  time.Now(),
				PrivateKey: "a very secret private key",
				PublicKey:  "a very public public key",
			},
			exp: audit.Map{
				"user_id":     uuid.UUID{1}.String(),
				"private_key": "",
				"public_key":  "a very public public key",
			},
		},
	})

	runDiffTests(t, []diffTest[database.OrganizationMember]{
		{
			name: "Create",
			left: audit.Empty[database.OrganizationMember](),
			right: database.OrganizationMember{
				UserID:         uuid.UUID{1},
				OrganizationID: uuid.UUID{2},
				CreatedAt:      time.Now(),
				UpdatedAt:      time.Now(),
				Roles:          []string{"auditor"},
			},
			exp: audit.Map{
				"user_id":         uuid.UUID{1}.String(),
				"organization_id": uuid.UUID{2}.String(),
				"roles":           []string{"auditor"},
			},
		},
	})

	runDiffTests(t, []diffTest[database.Organization]{
		{
			name: "Create",
			left: audit.Empty[database.Organization](),
			right: database.Organization{
				ID:          uuid.UUID{1},
				Name:        "rust developers",
				Description: "an organization for rust developers",
				CreatedAt:   time.Now(),
				UpdatedAt:   time.Now(),
			},
			exp: audit.Map{
				"id":          uuid.UUID{1}.String(),
				"name":        "rust developers",
				"description": "an organization for rust developers",
			},
		},
	})

	runDiffTests(t, []diffTest[database.Template]{
		{
			name: "Create",
			left: audit.Empty[database.Template](),
			right: database.Template{
				ID:                   uuid.UUID{1},
				CreatedAt:            time.Now(),
				UpdatedAt:            time.Now(),
				OrganizationID:       uuid.UUID{2},
				Deleted:              false,
				Name:                 "rust",
				Provisioner:          database.ProvisionerTypeTerraform,
				ActiveVersionID:      uuid.UUID{3},
				MaxTtl:               int64(time.Hour),
				MinAutostartInterval: int64(time.Minute),
				CreatedBy:            uuid.UUID{4},
			},
			exp: audit.Map{
				"id":                     uuid.UUID{1}.String(),
				"organization_id":        uuid.UUID{2}.String(),
				"name":                   "rust",
				"provisioner":            database.ProvisionerTypeTerraform,
				"active_version_id":      uuid.UUID{3}.String(),
				"max_ttl":                int64(3600000000000),
				"min_autostart_interval": int64(60000000000),
				"created_by":             uuid.UUID{4}.String(),
			},
		},
	})

	runDiffTests(t, []diffTest[database.TemplateVersion]{
		{
			name: "Create",
			left: audit.Empty[database.TemplateVersion](),
			right: database.TemplateVersion{
				ID:             uuid.UUID{1},
				TemplateID:     uuid.NullUUID{UUID: uuid.UUID{2}, Valid: true},
				CreatedAt:      time.Now(),
				UpdatedAt:      time.Now(),
				OrganizationID: uuid.UUID{3},
				Name:           "rust",
				CreatedBy:      uuid.NullUUID{UUID: uuid.UUID{4}, Valid: true},
			},
			exp: audit.Map{
				"id":              uuid.UUID{1}.String(),
				"template_id":     uuid.UUID{2}.String(),
				"organization_id": uuid.UUID{3}.String(),
				"name":            "rust",
				"created_by":      uuid.UUID{4}.String(),
			},
		},
		{
			name: "CreateNullTemplateID",
			left: audit.Empty[database.TemplateVersion](),
			right: database.TemplateVersion{
				ID:             uuid.UUID{1},
				TemplateID:     uuid.NullUUID{},
				CreatedAt:      time.Now(),
				UpdatedAt:      time.Now(),
				OrganizationID: uuid.UUID{3},
				Name:           "rust",
				CreatedBy:      uuid.NullUUID{UUID: uuid.UUID{4}, Valid: true},
			},
			exp: audit.Map{
				"id":              uuid.UUID{1}.String(),
				"organization_id": uuid.UUID{3}.String(),
				"name":            "rust",
				"created_by":      uuid.UUID{4}.String(),
			},
		},
	})

	runDiffTests(t, []diffTest[database.User]{
		{
			name: "Create",
			left: audit.Empty[database.User](),
			right: database.User{
				ID:             uuid.UUID{1},
				Email:          "colin@coder.com",
				Username:       "colin",
				HashedPassword: []byte("hunter2ButHashed"),
				CreatedAt:      time.Now(),
				UpdatedAt:      time.Now(),
				Status:         database.UserStatusActive,
				RBACRoles:      []string{"omega admin"},
			},
			exp: audit.Map{
				"id":              uuid.UUID{1}.String(),
				"email":           "colin@coder.com",
				"username":        "colin",
				"hashed_password": ([]byte)(nil),
				"status":          database.UserStatusActive,
				"rbac_roles":      []string{"omega admin"},
			},
		},
	})

	runDiffTests(t, []diffTest[database.Workspace]{
		{
			name: "Create",
			left: audit.Empty[database.Workspace](),
			right: database.Workspace{
				ID:                uuid.UUID{1},
				CreatedAt:         time.Now(),
				UpdatedAt:         time.Now(),
				OwnerID:           uuid.UUID{2},
				TemplateID:        uuid.UUID{3},
				Name:              "rust workspace",
				AutostartSchedule: sql.NullString{String: "0 12 * * 1-5", Valid: true},
				Ttl:               sql.NullInt64{Int64: int64(8 * time.Hour), Valid: true},
			},
			exp: audit.Map{
				"id":                 uuid.UUID{1}.String(),
				"owner_id":           uuid.UUID{2}.String(),
				"template_id":        uuid.UUID{3}.String(),
				"name":               "rust workspace",
				"autostart_schedule": "0 12 * * 1-5",
				"ttl":                int64(28800000000000), // XXX: pq still does not support time.Duration
			},
		},
		{
			name: "NullSchedules",
			left: audit.Empty[database.Workspace](),
			right: database.Workspace{
				ID:                uuid.UUID{1},
				CreatedAt:         time.Now(),
				UpdatedAt:         time.Now(),
				OwnerID:           uuid.UUID{2},
				TemplateID:        uuid.UUID{3},
				Name:              "rust workspace",
				AutostartSchedule: sql.NullString{},
				Ttl:               sql.NullInt64{},
			},
			exp: audit.Map{
				"id":          uuid.UUID{1}.String(),
				"owner_id":    uuid.UUID{2}.String(),
				"template_id": uuid.UUID{3}.String(),
				"name":        "rust workspace",
			},
		},
	})
}

type diffTest[T audit.Auditable] struct {
	name        string
	left, right T
	exp         audit.Map
}

func runDiffTests[T audit.Auditable](t *testing.T, tests []diffTest[T]) {
	t.Helper()

	var typ T
	typName := reflect.TypeOf(typ).Name()

	for _, test := range tests {
		t.Run(typName+"/"+test.name, func(t *testing.T) {
			t.Parallel()
			require.Equal(t,
				test.exp,
				audit.Diff(test.left, test.right),
			)
		})
	}
}
