package metadata

import (
	"context"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
)

// fakeLayoutSource records which read the adapter reached. The whole point of
// the authoring read is that it is a DIFFERENT read from GetLayoutByID, so a
// pass-through wired to the wrong one is exactly the mistake worth catching —
// and it is otherwise invisible, because both methods have the same signature
// and, on a layout with no autonumber fields, return the same value.
type fakeLayoutSource struct {
	fakeContractMetadataSource

	calls     []string
	authoring *models.PageLayout
	presented *models.PageLayout
}

func (f *fakeLayoutSource) GetAllLayouts(context.Context, string) ([]*models.PageLayout, error) {
	f.calls = append(f.calls, "GetAllLayouts")
	return nil, nil
}

func (f *fakeLayoutSource) GetLayoutByID(context.Context, string) (*models.PageLayout, error) {
	f.calls = append(f.calls, "GetLayoutByID")
	return f.presented, nil
}

func (f *fakeLayoutSource) GetLayoutForAuthoring(context.Context, string) (*models.PageLayout, error) {
	f.calls = append(f.calls, "GetLayoutForAuthoring")
	return f.authoring, nil
}

func (f *fakeLayoutSource) GetAssignmentsForObject(context.Context, string) ([]*models.LayoutAssignment, error) {
	return nil, nil
}

func (f *fakeLayoutSource) GetAllApprovalLayouts(context.Context, string) ([]*models.ApprovalPageLayout, error) {
	return nil, nil
}

func (f *fakeLayoutSource) GetApprovalLayoutByID(context.Context, string) (*models.ApprovalPageLayout, error) {
	return nil, nil
}

func TestContractServiceAuthoringReadReachesTheAuthoringSource(t *testing.T) {
	source := &fakeLayoutSource{
		authoring: &models.PageLayout{ID: "L1", LayoutName: "stored"},
		presented: &models.PageLayout{ID: "L1", LayoutName: "presented"},
	}
	svc := NewContractService(source, nil)

	got, err := svc.GetLayoutForAuthoring(context.Background(), "L1")
	if err != nil {
		t.Fatalf("GetLayoutForAuthoring: %v", err)
	}
	if got == nil || got.LayoutName != "stored" {
		t.Fatalf("adapter returned %#v, want the authoring source's layout", got)
	}
	if len(source.calls) != 1 || source.calls[0] != "GetLayoutForAuthoring" {
		t.Fatalf("adapter reached %v, want exactly [GetLayoutForAuthoring]", source.calls)
	}
}

func TestContractServicePresentationReadStillReachesThePresentationSource(t *testing.T) {
	source := &fakeLayoutSource{
		authoring: &models.PageLayout{ID: "L1", LayoutName: "stored"},
		presented: &models.PageLayout{ID: "L1", LayoutName: "presented"},
	}
	svc := NewContractService(source, nil)

	got, err := svc.GetLayoutByID(context.Background(), "L1")
	if err != nil {
		t.Fatalf("GetLayoutByID: %v", err)
	}
	if got == nil || got.LayoutName != "presented" {
		t.Fatalf("adapter returned %#v, want the presentation source's layout", got)
	}
	if len(source.calls) != 1 || source.calls[0] != "GetLayoutByID" {
		t.Fatalf("adapter reached %v, want exactly [GetLayoutByID]", source.calls)
	}
}

// legacyLayoutSource implements every layout read that existed before the
// authoring capability, and none of the new one. This is the shape that decides
// whether the authoring split is real: the adapter discovers layout capability
// through comma-ok, and a single shared assertion would make this source lose
// ALL SIX layout reads rather than just the one it genuinely cannot serve.
//
// Every layout read is declared HERE rather than inherited from the embedded
// fake: this double must stay frozen at the pre-authoring shape, or it stops
// testing anything. Do not add new capabilities to it when they are added to
// the interface — that is precisely the change it exists to catch.
type legacyLayoutSource struct {
	fakeContractMetadataSource

	presented *models.PageLayout
}

func (f *legacyLayoutSource) GetAllLayouts(context.Context, string) ([]*models.PageLayout, error) {
	return []*models.PageLayout{f.presented}, nil
}

func (f *legacyLayoutSource) GetLayoutByID(context.Context, string) (*models.PageLayout, error) {
	return f.presented, nil
}

func (f *legacyLayoutSource) GetAssignmentsForObject(context.Context, string) ([]*models.LayoutAssignment, error) {
	return []*models.LayoutAssignment{{ID: "A1"}}, nil
}

func (f *legacyLayoutSource) GetAllApprovalLayouts(context.Context, string) ([]*models.ApprovalPageLayout, error) {
	return []*models.ApprovalPageLayout{{ID: "AP1"}}, nil
}

func (f *legacyLayoutSource) GetApprovalLayoutByID(context.Context, string) (*models.ApprovalPageLayout, error) {
	return &models.ApprovalPageLayout{ID: "AP1"}, nil
}

func TestContractServiceLegacySourceKeepsEveryPreExistingLayoutRead(t *testing.T) {
	source := &legacyLayoutSource{presented: &models.PageLayout{ID: "L1", LayoutName: "presented"}}
	svc := NewContractService(source, nil)
	ctx := context.Background()

	// t.Errorf, not t.Fatalf: a regression here breaks every read at once, and
	// one run should enumerate all of them rather than stopping at the first.
	if got, err := svc.ListLayouts(ctx, "Account"); err != nil || len(got) != 1 {
		t.Errorf("ListLayouts = (%v, %v), want one layout and no error", got, err)
	}
	if got, err := svc.GetLayoutByID(ctx, "L1"); err != nil || got == nil {
		t.Errorf("GetLayoutByID = (%v, %v), want a layout and no error", got, err)
	}
	if got, err := svc.ListLayoutAssignments(ctx, "Account"); err != nil || len(got) != 1 {
		t.Errorf("ListLayoutAssignments = (%v, %v), want one assignment and no error", got, err)
	}
	if got, err := svc.ListApprovalLayouts(ctx, "Account"); err != nil || len(got) != 1 {
		t.Errorf("ListApprovalLayouts = (%v, %v), want one layout and no error", got, err)
	}
	if got, err := svc.GetApprovalLayoutByID(ctx, "AP1"); err != nil || got == nil {
		t.Errorf("GetApprovalLayoutByID = (%v, %v), want a layout and no error", got, err)
	}

	// Only the capability the source genuinely lacks degrades — and it must
	// degrade through the AUTHORING assertion. Asserting merely that an error
	// occurred would pass equally well in the merged-interface state this test
	// exists to reject, because there every read fails.
	layout, err := svc.GetLayoutForAuthoring(ctx, "L1")
	if err == nil || !strings.Contains(err.Error(), "layout authoring source unavailable") {
		t.Errorf("GetLayoutForAuthoring error = %v, want the authoring-source degrade", err)
	}
	if layout != nil {
		t.Errorf("GetLayoutForAuthoring returned %#v alongside an error, want nil", layout)
	}
}

// A source that cannot serve layouts must produce an error, not a nil layout
// that a caller would mistake for "no such layout" and then persist over.
func TestContractServiceAuthoringReadErrorsWhenSourceLacksLayoutCapability(t *testing.T) {
	svc := NewContractService(&fakeContractMetadataSource{}, nil)

	got, err := svc.GetLayoutForAuthoring(context.Background(), "L1")
	if err == nil {
		t.Fatal("expected an error when the source has no layout capability")
	}
	if got != nil {
		t.Fatalf("expected a nil layout alongside the error, got %#v", got)
	}
}
