// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package statemgr

import (
	"context"
	"encoding/json"
	"flag"
	"os"
	"testing"
	"time"

	_ "github.com/opentofu/opentofu/internal/logging"
)

func TestNewLockInfo(t *testing.T) {
	info1 := NewLockInfo()
	info2 := NewLockInfo()

	if info1.ID == "" {
		t.Fatal("LockInfo missing ID")
	}

	if info1.Version == "" {
		t.Fatal("LockInfo missing version")
	}

	if info1.Created.IsZero() {
		t.Fatal("LockInfo missing Created")
	}

	if info1.ID == info2.ID {
		t.Fatal("multiple LockInfo with identical IDs")
	}

	// test the JSON output is valid
	newInfo := &LockInfo{}
	err := json.Unmarshal(info1.Marshal(), newInfo)
	if err != nil {
		t.Fatal(err)
	}
}

func TestLockWithContext(t *testing.T) {
	s := NewFullFake(nil, TestFullInitialState())

	id, err := s.Lock(NewLockInfo())
	if err != nil {
		t.Fatal(err)
	}

	// use a cancelled context for an immediate timeout
	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	info := NewLockInfo()
	info.Info = "lock with context"
	_, err = LockWithContext(ctx, s, info)
	if err == nil {
		t.Fatal("lock should have failed immediately")
	}

	// block until LockwithContext has made a first attempt
	attempted := make(chan struct{})
	postLockHook = func() {
		close(attempted)
		postLockHook = nil
	}

	// unlock the state during LockWithContext
	unlocked := make(chan struct{})
	var unlockErr error
	go func() {
		defer close(unlocked)
		<-attempted
		unlockErr = s.Unlock(id)
	}()

	ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	id, err = LockWithContext(ctx, s, info)
	if err != nil {
		t.Fatal("lock should have completed within 2s:", err)
	}

	// ensure the goruotine completes
	<-unlocked
	if unlockErr != nil {
		t.Fatal(unlockErr)
	}
}

func TestMain(m *testing.M) {
	flag.Parse()
	os.Exit(m.Run())
}
