package metadata

import (
	"context"
	"database/sql"
	"encoding/json"
	"testing"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/shared/pkg/models"
)

// restoreLayoutRecordStateRepo is a minimal in-memory repo for RestoreLayout tests.
type restoreLayoutRecordStateRepo struct {
	ports.MetadataRepository

	layout      *models.PageLayout
	layoutErr   error
	historyJSON string
	historyErr  error
	upserted    *models.PageLayout
}

func (r *restoreLayoutRecordStateRepo) GetLayout(ctx context.Context, id string) (*models.PageLayout, error) {
	if r.layoutErr != nil {
		return nil, r.layoutErr
	}
	if r.layout != nil && r.layout.ID == id {
		cp := *r.layout
		return &cp, nil
	}
	return nil, nil
}

func (r *restoreLayoutRecordStateRepo) UpsertLayout(ctx context.Context, layout *models.PageLayout) error {
	cp := *layout
	r.upserted = &cp
	return nil
}

func (r *restoreLayoutRecordStateRepo) SaveLayout(ctx context.Context, layout *models.PageLayout) error {
	cp := *layout
	r.upserted = &cp
	return nil
}

func (r *restoreLayoutRecordStateRepo) GetLayoutHistoryConfig(ctx context.Context, layoutID string, versionNumber int) (string, error) {
	if r.historyErr != nil {
		return "", r.historyErr
	}
	return r.historyJSON, nil
}

func (r *restoreLayoutRecordStateRepo) WriteLayoutHistory(ctx context.Context, entry *models.PageLayoutHistoryEntry) error {
	return nil
}

func (r *restoreLayoutRecordStateRepo) GetLayoutHistoryMaxVersion(ctx context.Context, layoutID string) (int, error) {
	return 1, nil
}

func (r *restoreLayoutRecordStateRepo) GetRelatedListConfigs(ctx context.Context, layoutObjectAPIName string) ([]ports.RelatedListConfig, error) {
	return nil, nil
}

func TestRestoreLayoutPreservesRecordStates(t *testing.T) {
	rules := []models.RecordStateRule{
		{
			ID:       "state-archived",
			When:     "record.Active == false",
			Priority: 5,
			Banner: &models.RecordStateBanner{
				Severity: "info",
				Title:    "Archived record",
			},
			ReadOnly: &models.RecordStateReadOnly{Enabled: true},
		},
	}

	historicalLayout := models.PageLayout{
		ID:            "layout-restore-test",
		ObjectAPIName: "Widget",
		LayoutName:    "Widget Layout",
		Sections: []models.PageSection{
			{ID: "s1", Label: "Details", Fields: []string{"Name"}},
		},
		RecordStates: rules,
	}
	histJSON, err := json.Marshal(historicalLayout)
	if err != nil {
		t.Fatalf("json.Marshal historical layout: %v", err)
	}

	currentLayout := models.PageLayout{
		ID:            "layout-restore-test",
		ObjectAPIName: "Widget",
		LayoutName:    "Widget Layout",
		Sections: []models.PageSection{
			{ID: "s1", Label: "Details", Fields: []string{"Name"}},
		},
		// RecordStates intentionally absent in "current" to confirm restore fills it in
	}

	repo := &restoreLayoutRecordStateRepo{
		layout:      &currentLayout,
		historyJSON: string(histJSON),
	}
	svc := NewMetadataService(repo, nil, nil)

	if err := svc.RestoreLayout(context.Background(), "layout-restore-test", 1); err != nil {
		t.Fatalf("RestoreLayout() error = %v", err)
	}

	if repo.upserted == nil {
		t.Fatal("UpsertLayout was not called after RestoreLayout")
	}
	restored := repo.upserted
	if len(restored.RecordStates) != 1 {
		t.Fatalf("RecordStates len = %d, want 1", len(restored.RecordStates))
	}
	got := restored.RecordStates[0]
	if got.ID != "state-archived" {
		t.Errorf("RecordStates[0].ID = %q, want state-archived", got.ID)
	}
	if got.ReadOnly == nil || !got.ReadOnly.Enabled {
		t.Error("RecordStates[0].ReadOnly.Enabled want true after RestoreLayout")
	}
	if got.Banner == nil || got.Banner.Severity != "info" {
		t.Errorf("RecordStates[0].Banner.Severity = %v, want info", got.Banner)
	}
}

func TestRestoreLayoutClearsRecordStatesWhenHistoryHasNone(t *testing.T) {
	historicalLayout := models.PageLayout{
		ID:            "layout-clear-test",
		ObjectAPIName: "Widget",
		LayoutName:    "Widget Layout",
		Sections: []models.PageSection{
			{ID: "s1", Label: "Details", Fields: []string{"Name"}},
		},
		// no RecordStates in history snapshot
	}
	histJSON, err := json.Marshal(historicalLayout)
	if err != nil {
		t.Fatalf("json.Marshal historical layout: %v", err)
	}

	currentLayout := models.PageLayout{
		ID:            "layout-clear-test",
		ObjectAPIName: "Widget",
		LayoutName:    "Widget Layout",
		RecordStates: []models.RecordStateRule{
			{ID: "stale-rule", When: "record.Deleted == true"},
		},
	}

	repo := &restoreLayoutRecordStateRepo{
		layout:      &currentLayout,
		historyJSON: string(histJSON),
	}
	svc := NewMetadataService(repo, nil, nil)

	if err := svc.RestoreLayout(context.Background(), "layout-clear-test", 1); err != nil {
		t.Fatalf("RestoreLayout() error = %v", err)
	}

	if repo.upserted == nil {
		t.Fatal("UpsertLayout was not called after RestoreLayout")
	}
	if len(repo.upserted.RecordStates) != 0 {
		t.Errorf("RecordStates len = %d after RestoreLayout with empty history, want 0", len(repo.upserted.RecordStates))
	}
}

func TestRestoreLayoutMissingHistoryReturnsNotFound(t *testing.T) {
	repo := &restoreLayoutRecordStateRepo{historyErr: sql.ErrNoRows}
	svc := NewMetadataService(repo, nil, nil)

	err := svc.RestoreLayout(context.Background(), "layout-missing-history", 7)
	if !apperrors.IsNotFound(err) {
		t.Fatalf("RestoreLayout() error = %T %v, want NotFoundError", err, err)
	}
}

func TestRestoreLayoutMissingCurrentLayoutReturnsNotFound(t *testing.T) {
	historicalLayout := models.PageLayout{ID: "layout-missing-current"}
	histJSON, err := json.Marshal(historicalLayout)
	if err != nil {
		t.Fatalf("json.Marshal historical layout: %v", err)
	}
	repo := &restoreLayoutRecordStateRepo{historyJSON: string(histJSON)}
	svc := NewMetadataService(repo, nil, nil)

	err = svc.RestoreLayout(context.Background(), "layout-missing-current", 1)
	if !apperrors.IsNotFound(err) {
		t.Fatalf("RestoreLayout() error = %T %v, want NotFoundError", err, err)
	}
}

func TestRestoreLayoutNoRowsCurrentLayoutReturnsNotFound(t *testing.T) {
	historicalLayout := models.PageLayout{ID: "layout-no-rows-current"}
	histJSON, err := json.Marshal(historicalLayout)
	if err != nil {
		t.Fatalf("json.Marshal historical layout: %v", err)
	}
	repo := &restoreLayoutRecordStateRepo{historyJSON: string(histJSON), layoutErr: sql.ErrNoRows}
	svc := NewMetadataService(repo, nil, nil)

	err = svc.RestoreLayout(context.Background(), "layout-no-rows-current", 1)
	if !apperrors.IsNotFound(err) {
		t.Fatalf("RestoreLayout() error = %T %v, want NotFoundError", err, err)
	}
}

func TestRestoreLayoutPreservesPrimaryHeaderActions(t *testing.T) {
	historicalLayout := models.PageLayout{
		ID:            "layout-restore-primary",
		ObjectAPIName: "Widget",
		LayoutName:    "Widget Layout",
		Sections: []models.PageSection{
			{ID: "s1", Label: "Details", Fields: []string{"Name"}},
		},
		PrimaryHeaderActions: []string{"standard-action:createQuote", "metadata-action:AddProducts"},
	}
	histJSON, err := json.Marshal(historicalLayout)
	if err != nil {
		t.Fatalf("json.Marshal historical layout: %v", err)
	}

	currentLayout := models.PageLayout{
		ID:            "layout-restore-primary",
		ObjectAPIName: "Widget",
		LayoutName:    "Widget Layout",
		Sections: []models.PageSection{
			{ID: "s1", Label: "Details", Fields: []string{"Name"}},
		},
		// PrimaryHeaderActions intentionally absent in "current" to confirm restore fills it in
	}

	repo := &restoreLayoutRecordStateRepo{
		layout:      &currentLayout,
		historyJSON: string(histJSON),
	}
	svc := NewMetadataService(repo, nil, nil)

	if err := svc.RestoreLayout(context.Background(), "layout-restore-primary", 1); err != nil {
		t.Fatalf("RestoreLayout() error = %v", err)
	}

	if repo.upserted == nil {
		t.Fatal("UpsertLayout was not called after RestoreLayout")
	}
	got := repo.upserted.PrimaryHeaderActions
	want := []string{"standard-action:createQuote", "metadata-action:AddProducts"}
	if len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
		t.Fatalf("PrimaryHeaderActions = %v, want %v", got, want)
	}
}
