package catalogreconcile

import (
	"bytes"
	"encoding/json"
	"strings"
	"testing"
	"time"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	consumerv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
)

func TestBuildEffectiveApplicationNavigationComponentIsExactAndDeterministic(
	t *testing.T,
) {
	width := 320
	applications := []*models.AppConfig{
		{
			ID: "sales", FrontendAppID: "sfcrm", Label: "Sales",
			Description: "Sell", Icon: "sales", Color: "blue", IsDefault: true,
			AssignedProfiles: []string{"admin"}, CreatedDate: time.Now(),
			NavigationItems: []models.NavigationItem{
				{ID: "account-tab", Type: constants.NavigationTypeObject, ObjectAPIName: "account", Label: "stale"},
				{ID: "home", Type: constants.NavigationTypePage, PageURL: "/home", Label: "Home"},
			},
			UtilityItems: []models.UtilityItem{{
				ID: "notes", Type: "NOTES", Label: "Notes", PanelWidth: &width,
			}},
		},
		{ID: "service", Label: "Service"},
	}
	objects := []*models.ObjectMetadata{{
		APIName: "Account", Label: "Account", PluralLabel: "Accounts",
	}}

	first, err := buildEffectiveApplicationNavigationComponent(applications, objects)
	if err != nil {
		t.Fatal(err)
	}
	second, err := buildEffectiveApplicationNavigationComponent(
		[]*models.AppConfig{applications[1], applications[0]}, objects,
	)
	if err != nil {
		t.Fatal(err)
	}
	if first.ComponentKind != consumerv1.EffectiveApplicationNavigationComponentKind ||
		first.BusinessKey != consumerv1.EffectiveApplicationNavigationComponentBusinessKey ||
		first.ComponentFormatVersion != 1 ||
		first.ContentDigest != second.ContentDigest ||
		!bytes.Equal(first.CanonicalBehavior, second.CanonicalBehavior) {
		t.Fatalf("application-navigation component is not canonical: first=%#v second=%#v", first, second)
	}
	if strings.Contains(string(first.CanonicalBehavior), "admin") ||
		strings.Contains(string(first.CanonicalBehavior), "isDefault") ||
		strings.Contains(string(first.CanonicalBehavior), "createdDate") {
		t.Fatalf("legacy subject authority leaked into Catalog definition: %s", first.CanonicalBehavior)
	}
	var document effectiveApplicationNavigationDocument
	if err := json.Unmarshal(first.CanonicalBehavior, &document); err != nil {
		t.Fatal(err)
	}
	if len(document.Applications) != 2 || document.Applications[0].ID != "sales" ||
		document.Applications[1].ID != "service" {
		t.Fatalf("applications = %#v", document.Applications)
	}
	sales := document.Applications[0]
	if sales.NavigationItems == nil || len(sales.NavigationItems) != 0 ||
		len(sales.UtilityItems) != 1 || sales.UtilityItems[0].Type != "NOTES" ||
		sales.UtilityItems[0].PanelWidth == nil || *sales.UtilityItems[0].PanelWidth != width {
		t.Fatalf("sales application projection = %#v", sales)
	}
}

func TestBuildEffectiveApplicationNavigationComponentValidatesShellOnly(t *testing.T) {
	tests := []struct {
		name         string
		applications []*models.AppConfig
		want         string
	}{
		{name: "nil application", applications: []*models.AppConfig{nil}, want: "is nil"},
		{name: "missing application identity", applications: []*models.AppConfig{{}}, want: "has no identity"},
		{
			name:         "duplicate application identity",
			applications: []*models.AppConfig{{ID: "Sales"}, {ID: "sales"}},
			want:         "duplicate application",
		},
		{
			name: "missing navigation identity",
			applications: []*models.AppConfig{{
				ID: "sales", NavigationItems: []models.NavigationItem{{Type: "object"}},
			}},
			want: "has no identity",
		},
		{
			name: "unsupported navigation type",
			applications: []*models.AppConfig{{
				ID: "sales", NavigationItems: []models.NavigationItem{{
					ID: "bad", Type: "future-unknown",
				}},
			}},
			want: "unsupported type",
		},
		{
			name: "object navigation has no object API name",
			applications: []*models.AppConfig{{
				ID: "sales", NavigationItems: []models.NavigationItem{{
					ID: "account", Type: constants.NavigationTypeObject,
				}},
			}},
			want: "has no object API name",
		},
		{
			name: "object navigation references unknown object",
			applications: []*models.AppConfig{{
				ID: "sales", NavigationItems: []models.NavigationItem{{
					ID: "account", Type: constants.NavigationTypeObject,
					ObjectAPIName: "Account",
				}},
			}},
			want: "references unknown object",
		},
		{
			name: "web navigation has no URL",
			applications: []*models.AppConfig{{
				ID: "sales", NavigationItems: []models.NavigationItem{{
					ID: "docs", Type: constants.NavigationTypeWeb,
				}},
			}},
			want: "has no page URL",
		},
		{
			name: "dashboard navigation has no identity",
			applications: []*models.AppConfig{{
				ID: "sales", NavigationItems: []models.NavigationItem{{
					ID: "pipeline", Type: constants.NavigationTypeDashboard,
				}},
			}},
			want: "has no dashboard identity",
		},
		{
			name: "duplicate navigation identity",
			applications: []*models.AppConfig{{
				ID: "sales", NavigationItems: []models.NavigationItem{
					{ID: "Home", Type: constants.NavigationTypePage},
					{ID: "home", Type: constants.NavigationTypePage},
				},
			}},
			want: "duplicate navigation item",
		},
		{
			name: "duplicate utility identity",
			applications: []*models.AppConfig{{
				ID: "sales", UtilityItems: []models.UtilityItem{
					{ID: "Notes", Type: "notes"},
					{ID: "notes", Type: "notes"},
				},
			}},
			want: "duplicate utility item",
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if strings.Contains(test.name, "navigation") {
				actual, err := buildEffectiveApplicationNavigationComponent(test.applications, nil)
				if err != nil {
					t.Fatalf("Catalog interpreted runtime navigation: %v", err)
				}
				empty, err := buildEffectiveApplicationNavigationComponent([]*models.AppConfig{{ID: "sales"}}, nil)
				if err != nil || string(actual.CanonicalBehavior) != string(empty.CanonicalBehavior) {
					t.Fatalf("navigation changed Catalog: %+v %v", actual, err)
				}
				return
			}
			_, err := buildEffectiveApplicationNavigationComponent(test.applications, nil)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("error = %v, want %q", err, test.want)
			}
		})
	}
}
