package records

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"testing"
	"time"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/kernel/pkg/recordlocks"
	"github.com/nexuscrm/persistence"
)

type fakeParentLaneAdmissionController struct {
	acquired        []recordlocks.ParentKey
	heartbeats      [][]persistence.ParentLaneOwnership
	heartbeatAdds   []persistence.ParentLaneOwnership
	releases        []persistence.ParentLaneOwnership
	acquireErrByID  map[string]error
	beforeAcquire   func(recordlocks.ParentKey) error
	heartbeatErr    error
	heartbeatAddErr error
	releaseFn       func(context.Context, persistence.ParentLaneOwnership) (bool, error)
}

func (c *fakeParentLaneAdmissionController) Acquire(_ context.Context, key recordlocks.ParentKey, _ persistence.ParentLaneAcquireOptions) (persistence.ParentLaneOwnership, error) {
	if c.beforeAcquire != nil {
		if err := c.beforeAcquire(key); err != nil {
			return persistence.ParentLaneOwnership{}, err
		}
	}
	if c.acquireErrByID != nil {
		if err := c.acquireErrByID[recordlocks.KeyID(key)]; err != nil {
			return persistence.ParentLaneOwnership{}, err
		}
	}
	c.acquired = append(c.acquired, key)
	hash, _ := recordlocks.ParentLaneKeyHash(key)
	return persistence.ParentLaneOwnership{
		Key:        key,
		KeyHash:    hash,
		OwnerToken: fmt.Sprintf("owner-%d", len(c.acquired)),
	}, nil
}

func (c *fakeParentLaneAdmissionController) StartHeartbeat(_ context.Context, owners []persistence.ParentLaneOwnership, _ persistence.ParentLaneHeartbeatOptions) (ParentLaneHeartbeatController, error) {
	if c.heartbeatErr != nil {
		return nil, c.heartbeatErr
	}
	copied := append([]persistence.ParentLaneOwnership(nil), owners...)
	c.heartbeats = append(c.heartbeats, copied)
	return fakeParentLaneHeartbeatController{admission: c}, nil
}

func (c *fakeParentLaneAdmissionController) Release(ctx context.Context, owner persistence.ParentLaneOwnership) (bool, error) {
	c.releases = append(c.releases, owner)
	if c.releaseFn != nil {
		return c.releaseFn(ctx, owner)
	}
	return true, nil
}

type fakeParentLaneHeartbeatController struct {
	admission *fakeParentLaneAdmissionController
}

func (c fakeParentLaneHeartbeatController) AddOwner(owner persistence.ParentLaneOwnership) error {
	if c.admission.heartbeatAddErr != nil {
		return c.admission.heartbeatAddErr
	}
	c.admission.heartbeatAdds = append(c.admission.heartbeatAdds, owner)
	return nil
}

func (fakeParentLaneHeartbeatController) Done() <-chan struct{} {
	done := make(chan struct{})
	close(done)
	return done
}

func parentLaneAdmissionTestContext() context.Context {
	return ports.WithTransactionAbort(ports.WithTransactionCompletionHooks(context.Background()))
}

func TestRunWithParentLaneAdmissionUsesInjectedController(t *testing.T) {
	controller := &fakeParentLaneAdmissionController{}
	ps := &PersistenceService{
		txManager: parentLockCoordinatorTxManager{active: true, tx: &struct{}{}},
	}
	ps.SetParentLaneAdmissionController(controller)

	called := false
	err := ps.RunWithParentLaneAdmission(parentLaneAdmissionTestContext(), []recordlocks.ParentKey{
		{ObjectName: "Task__c", RecordID: "task-2"},
		{ObjectName: "Account", RecordID: "acct-1"},
		{ObjectName: "Contact", RecordID: "contact-1"},
		{ObjectName: "", RecordID: "ignored"},
	}, func(context.Context) error {
		called = true
		return nil
	})
	if err != nil {
		t.Fatalf("RunWithParentLaneAdmission returned error: %v", err)
	}
	if !called {
		t.Fatal("RunWithParentLaneAdmission did not call admitted function")
	}

	want := []recordlocks.ParentKey{
		{ObjectName: "Account", RecordID: "acct-1"},
		{ObjectName: "Contact", RecordID: "contact-1"},
		{ObjectName: "Task__c", RecordID: "task-2"},
	}
	if fmt.Sprint(controller.acquired) != fmt.Sprint(want) {
		t.Fatalf("acquired keys = %#v, want %#v", controller.acquired, want)
	}
	if len(controller.heartbeats) != 1 || len(controller.heartbeats[0]) != 1 || controller.heartbeats[0][0].Key != want[0] {
		t.Fatalf("heartbeats = %#v, want the first ordered owner armed before later acquisition", controller.heartbeats)
	}
	if len(controller.heartbeatAdds) != len(want)-1 || controller.heartbeatAdds[0].Key != want[1] || controller.heartbeatAdds[1].Key != want[2] {
		t.Fatalf("heartbeat additions = %#v, want later ordered owners %#v", controller.heartbeatAdds, want[1:])
	}
	if len(controller.releases) != 0 {
		t.Fatalf("releases = %#v, want none before transaction completion", controller.releases)
	}
}

func TestRunWithParentLaneAdmissionMissingControllerFailsClosed(t *testing.T) {
	ps := &PersistenceService{
		txManager: parentLockCoordinatorTxManager{active: true, tx: &struct{}{}},
	}

	called := false
	err := ps.RunWithParentLaneAdmission(parentLaneAdmissionTestContext(), []recordlocks.ParentKey{
		{ObjectName: "Account", RecordID: "acct-1"},
	}, func(context.Context) error {
		called = true
		return nil
	})
	if err == nil {
		t.Fatal("RunWithParentLaneAdmission returned nil, want fail-closed error")
	}
	if !appErrors.IsInternal(err) || !strings.Contains(err.Error(), "configured controller") {
		t.Fatalf("RunWithParentLaneAdmission error = %T %v, want internal configured-controller error", err, err)
	}
	if called {
		t.Fatal("RunWithParentLaneAdmission called fn despite missing controller")
	}
}

func TestRunWithParentLaneAdmissionReleasesAcquiredLanesOnAcquireFailure(t *testing.T) {
	controller := &fakeParentLaneAdmissionController{
		acquireErrByID: map[string]error{
			recordlocks.KeyID(recordlocks.ParentKey{ObjectName: "Contact", RecordID: "contact-1"}): errors.New("boom"),
		},
	}
	ps := &PersistenceService{txManager: parentLockCoordinatorTxManager{active: true, tx: &struct{}{}}, parentLaneAdmission: controller}

	called := false
	err := ps.RunWithParentLaneAdmission(parentLaneAdmissionTestContext(), []recordlocks.ParentKey{
		{ObjectName: "Contact", RecordID: "contact-1"},
		{ObjectName: "Account", RecordID: "acct-1"},
	}, func(context.Context) error {
		called = true
		return nil
	})
	if err == nil {
		t.Fatal("RunWithParentLaneAdmission returned nil, want acquire failure")
	}
	if called {
		t.Fatal("RunWithParentLaneAdmission called fn after acquire failure")
	}
	if len(controller.acquired) != 1 || controller.acquired[0].ObjectName != "Account" || controller.acquired[0].RecordID != "acct-1" {
		t.Fatalf("acquired = %#v, want only ordered Account/acct-1 before failure", controller.acquired)
	}
	if len(controller.releases) != 1 || controller.releases[0].Key.ObjectName != "Account" || controller.releases[0].Key.RecordID != "acct-1" {
		t.Fatalf("releases = %#v, want acquired Account/acct-1 released", controller.releases)
	}
	if len(controller.heartbeats) != 1 || len(controller.heartbeats[0]) != 1 || controller.heartbeats[0][0].Key != controller.acquired[0] {
		t.Fatalf("heartbeats = %#v, want the acquired owner armed before later acquisition", controller.heartbeats)
	}
}

func TestRunWithParentLaneAdmissionArmsFirstOwnerBeforeLaterAcquire(t *testing.T) {
	first := recordlocks.ParentKey{ObjectName: "Account", RecordID: "acct-1"}
	later := recordlocks.ParentKey{ObjectName: "Contact", RecordID: "contact-1"}
	controller := &fakeParentLaneAdmissionController{}
	controller.beforeAcquire = func(key recordlocks.ParentKey) error {
		if key != later {
			return nil
		}
		if len(controller.heartbeats) != 1 || len(controller.heartbeats[0]) != 1 || controller.heartbeats[0][0].Key != first {
			return errors.New("first parent lane was not armed before later acquire")
		}
		return nil
	}
	ps := &PersistenceService{txManager: parentLockCoordinatorTxManager{active: true, tx: &struct{}{}}, parentLaneAdmission: controller}

	err := ps.RunWithParentLaneAdmission(parentLaneAdmissionTestContext(), []recordlocks.ParentKey{first, later}, func(context.Context) error {
		return nil
	})
	if err != nil {
		t.Fatalf("RunWithParentLaneAdmission returned error: %v", err)
	}
	if len(controller.heartbeatAdds) != 1 || controller.heartbeatAdds[0].Key != later {
		t.Fatalf("heartbeat additions = %#v, want later owner %#v", controller.heartbeatAdds, later)
	}
}

func TestRunWithParentLaneAdmissionReleasesAcquiredLanesOnHeartbeatFailure(t *testing.T) {
	controller := &fakeParentLaneAdmissionController{heartbeatErr: errors.New("heartbeat setup failed")}
	ps := &PersistenceService{txManager: parentLockCoordinatorTxManager{active: true, tx: &struct{}{}}, parentLaneAdmission: controller}

	called := false
	err := ps.RunWithParentLaneAdmission(parentLaneAdmissionTestContext(), []recordlocks.ParentKey{
		{ObjectName: "Account", RecordID: "acct-1"},
		{ObjectName: "Contact", RecordID: "contact-1"},
	}, func(context.Context) error {
		called = true
		return nil
	})
	if err == nil {
		t.Fatal("RunWithParentLaneAdmission returned nil, want heartbeat failure")
	}
	if called {
		t.Fatal("RunWithParentLaneAdmission called fn after heartbeat failure")
	}
	if len(controller.acquired) != 1 {
		t.Fatalf("acquired = %#v, want only the first owner before heartbeat failure", controller.acquired)
	}
	if len(controller.releases) != 1 {
		t.Fatalf("releases = %#v, want the first acquired lane released", controller.releases)
	}
}

func TestRunWithParentLaneAdmissionReleasesAcquiredLanesOnHeartbeatAddFailure(t *testing.T) {
	controller := &fakeParentLaneAdmissionController{heartbeatAddErr: errors.New("heartbeat add failed")}
	ps := &PersistenceService{txManager: parentLockCoordinatorTxManager{active: true, tx: &struct{}{}}, parentLaneAdmission: controller}

	err := ps.RunWithParentLaneAdmission(parentLaneAdmissionTestContext(), []recordlocks.ParentKey{
		{ObjectName: "Account", RecordID: "acct-1"},
		{ObjectName: "Contact", RecordID: "contact-1"},
	}, func(context.Context) error {
		t.Fatal("admitted function should not run after heartbeat add failure")
		return nil
	})
	if err == nil || !strings.Contains(err.Error(), "heartbeat add failed") {
		t.Fatalf("RunWithParentLaneAdmission error = %v, want heartbeat add failure", err)
	}
	if len(controller.heartbeats) != 1 || len(controller.heartbeats[0]) != 1 {
		t.Fatalf("heartbeats = %#v, want the first owner armed", controller.heartbeats)
	}
	if len(controller.acquired) != 2 {
		t.Fatalf("acquired = %#v, want both owners acquired", controller.acquired)
	}
	if len(controller.releases) != 2 {
		t.Fatalf("releases = %#v, want both acquired lanes released", controller.releases)
	}
}

func TestRunWithParentLaneAdmissionCleanupReleaseIsBoundedAndObserved(t *testing.T) {
	oldTimeout := parentLaneAdmissionCleanupReleaseTimeout
	parentLaneAdmissionCleanupReleaseTimeout = time.Nanosecond
	defer func() { parentLaneAdmissionCleanupReleaseTimeout = oldTimeout }()

	var events []parentLaneAdmissionCleanupEvent
	oldObserver := observeParentLaneAdmissionCleanup
	observeParentLaneAdmissionCleanup = func(_ context.Context, event parentLaneAdmissionCleanupEvent) {
		events = append(events, event)
	}
	defer func() { observeParentLaneAdmissionCleanup = oldObserver }()

	started := make(chan struct{})
	done := make(chan struct{})
	controller := &fakeParentLaneAdmissionController{
		acquireErrByID: map[string]error{
			recordlocks.KeyID(recordlocks.ParentKey{ObjectName: "Contact", RecordID: "contact-1"}): errors.New("acquire failed"),
		},
		releaseFn: func(ctx context.Context, _ persistence.ParentLaneOwnership) (bool, error) {
			close(started)
			<-ctx.Done()
			close(done)
			return false, ctx.Err()
		},
	}
	ps := &PersistenceService{txManager: parentLockCoordinatorTxManager{active: true, tx: &struct{}{}}, parentLaneAdmission: controller}

	err := ps.RunWithParentLaneAdmission(parentLaneAdmissionTestContext(), []recordlocks.ParentKey{
		{ObjectName: "Contact", RecordID: "contact-1"},
		{ObjectName: "Account", RecordID: "acct-1"},
	}, func(context.Context) error {
		t.Fatal("admitted function should not run after acquire failure")
		return nil
	})
	if err == nil || !strings.Contains(err.Error(), "acquire failed") {
		t.Fatalf("RunWithParentLaneAdmission error = %v, want original acquire failure", err)
	}
	select {
	case <-started:
	default:
		t.Fatal("cleanup release was not attempted")
	}
	select {
	case <-done:
	default:
		t.Fatal("cleanup release did not return after bounded context timeout")
	}
	if len(events) != 1 || events[0].result != "failure" || events[0].err == nil {
		t.Fatalf("cleanup events = %#v, want one failure event with error", events)
	}
}

func TestRunWithParentLaneAdmissionCleanupObservesStaleOwner(t *testing.T) {
	var events []parentLaneAdmissionCleanupEvent
	oldObserver := observeParentLaneAdmissionCleanup
	observeParentLaneAdmissionCleanup = func(_ context.Context, event parentLaneAdmissionCleanupEvent) {
		events = append(events, event)
	}
	defer func() { observeParentLaneAdmissionCleanup = oldObserver }()

	controller := &fakeParentLaneAdmissionController{
		heartbeatErr: errors.New("heartbeat setup failed"),
		releaseFn: func(context.Context, persistence.ParentLaneOwnership) (bool, error) {
			return false, nil
		},
	}
	ps := &PersistenceService{txManager: parentLockCoordinatorTxManager{active: true, tx: &struct{}{}}, parentLaneAdmission: controller}

	err := ps.RunWithParentLaneAdmission(parentLaneAdmissionTestContext(), []recordlocks.ParentKey{
		{ObjectName: "Account", RecordID: "acct-1"},
	}, func(context.Context) error {
		t.Fatal("admitted function should not run after heartbeat failure")
		return nil
	})
	if err == nil {
		t.Fatal("RunWithParentLaneAdmission returned nil, want heartbeat failure")
	}
	if len(events) != 1 || events[0].result != "stale_owner" || events[0].keyHash == "" {
		t.Fatalf("cleanup events = %#v, want one stale_owner event with key hash", events)
	}
}
