package tenantoverlay

import (
	"bytes"
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/nexuscrm/backend/internal/controlplane/packs"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestPageLayoutOverlays_OpportunityEntry_ReplacesFullPingCAPTopology(t *testing.T) {
	overlayData, err := decodePageLayoutOverlays(readTestOverlayFile(t, PageLayoutOverlaysFile))
	if err != nil {
		t.Fatalf("parse %s: %v", PageLayoutOverlaysFile, err)
	}
	if len(overlayData.Layouts) < 2 {
		t.Fatalf("layout count = %d, want at least 2", len(overlayData.Layouts))
	}

	overlay := mustLoadPageLayoutOverlayByID(t, packs.CuratedOpportunityLayoutID)
	if overlay.ObjectAPIName != "Opportunity" {
		t.Fatalf("objectApiName = %q, want Opportunity", overlay.ObjectAPIName)
	}
	if overlay.LayoutID != packs.CuratedOpportunityLayoutID {
		t.Fatalf("layoutId = %q, want %q", overlay.LayoutID, packs.CuratedOpportunityLayoutID)
	}

	wantTabs := []string{
		"Details",
		"Why TiDB & Why Now",
		"Qualification & MEDDPICC",
		"Technical Information",
		"Activity",
		"AWS SRRP",
		"Chorus",
	}
	if len(overlay.TabsReplace) != len(wantTabs) {
		t.Fatalf("tabsReplace count = %d, want %d", len(overlay.TabsReplace), len(wantTabs))
	}
	for i, want := range wantTabs {
		if overlay.TabsReplace[i] != want {
			t.Fatalf("tabsReplace[%d] = %q, want %q", i, overlay.TabsReplace[i], want)
		}
	}

	wantCompact := []string{
		"AccountId",
		"StageName",
		"Probability",
		"ARR_Amount_USD__c",
		"ACV_Amount_USD__c",
		"Launch_Date__c",
	}
	if len(overlay.CompactLayoutReplace) != len(wantCompact) {
		t.Fatalf("compactLayoutReplace count = %d, want %d", len(overlay.CompactLayoutReplace), len(wantCompact))
	}
	for i, want := range wantCompact {
		if overlay.CompactLayoutReplace[i] != want {
			t.Fatalf("compactLayoutReplace[%d] = %q, want %q", i, overlay.CompactLayoutReplace[i], want)
		}
	}

	if len(overlay.SectionsReplace) != 14 {
		t.Fatalf("sectionsReplace count = %d, want 14", len(overlay.SectionsReplace))
	}
	details := overlay.SectionsReplace[0]
	if details.ID != "curated_Opportunity_section_details_information" {
		t.Fatalf("sectionsReplace[0].ID = %q, want details_information", details.ID)
	}
	if got := details.Fields[len(details.Fields)-2:]; len(got) != 2 ||
		got[0] != "PingCAP_Contracting_Entity_EMEA_only__c" ||
		got[1] != "AliCloud_Channel__c" {
		t.Fatalf("details fields tail = %v, want PingCAP/AliCloud fields", got)
	}

	why := overlay.SectionsReplace[7]
	if why.Label != "Why TiDB & Why Now" {
		t.Fatalf("why label = %q, want Why TiDB & Why Now", why.Label)
	}
	if why.Tab == nil || *why.Tab != "Why TiDB & Why Now" {
		t.Fatalf("why tab = %v, want Why TiDB & Why Now", why.Tab)
	}
	if got := why.Fields; len(got) != 6 || got[1] != "Why_TiDB__c" || got[2] != "Why_TiDB_Average_Score__c" {
		t.Fatalf("why fields = %v, want TiDB fields in full replacement", got)
	}

	technical := overlay.SectionsReplace[9]
	if got := technical.Fields[len(technical.Fields)-1]; got != "TiDB_Deployment_Estimate__c" {
		t.Fatalf("technical final field = %q, want TiDB_Deployment_Estimate__c", got)
	}
}

func TestPageLayoutOverlays_OrderEntry_PreservesArchivedFieldExposure(t *testing.T) {
	overlay := mustLoadPageLayoutOverlayByObject(t, "Order")
	if overlay.ObjectAPIName != "Order" {
		t.Fatalf("objectApiName = %q, want Order", overlay.ObjectAPIName)
	}
	if overlay.LayoutID != "" {
		t.Fatalf("layoutId = %q, want empty for implicit managed-target resolution", overlay.LayoutID)
	}
	if len(overlay.SectionsReplace) != 4 {
		t.Fatalf("sectionsReplace count = %d, want 4", len(overlay.SectionsReplace))
	}

	orderInfo := overlay.SectionsReplace[0]
	if orderInfo.ID != "sec_order_info" {
		t.Fatalf("sectionsReplace[0].ID = %q, want sec_order_info", orderInfo.ID)
	}
	wantFields := []string{
		"OrderNumber",
		"AccountId",
		"ContractId",
		"Status",
		"Archived_Order_Form__c",
		"StandardUploadUrl__c",
		"Archived__c",
		"EffectiveDate",
		"EndDate",
		"OwnerId",
	}
	if len(orderInfo.Fields) != len(wantFields) {
		t.Fatalf("sec_order_info field count = %d, want %d", len(orderInfo.Fields), len(wantFields))
	}
	for i, want := range wantFields {
		if orderInfo.Fields[i] != want {
			t.Fatalf("sec_order_info fields[%d] = %q, want %q", i, orderInfo.Fields[i], want)
		}
	}
}

func TestMergePageLayoutOverlay_ReplacesBaselineAndIsIdempotent(t *testing.T) {
	layout := models.PageLayout{
		Tabs:          []string{"Details"},
		CompactLayout: []string{"AccountId", "StageName", "Amount", "CloseDate"},
		Sections: []models.PageSection{
			{
				ID:     "curated_Opportunity_section_details_information",
				Label:  "Opportunity Information",
				Tab:    stringPtr("Details"),
				Fields: []string{"Name", "AccountId"},
			},
		},
	}

	overlay := mustLoadPageLayoutOverlayByID(t, packs.CuratedOpportunityLayoutID)
	if err := mergePageLayoutOverlay(&layout, overlay); err != nil {
		t.Fatalf("first mergePageLayoutOverlay() error = %v", err)
	}
	first, err := json.Marshal(layout)
	if err != nil {
		t.Fatalf("marshal first layout: %v", err)
	}

	if err := mergePageLayoutOverlay(&layout, overlay); err != nil {
		t.Fatalf("second mergePageLayoutOverlay() error = %v", err)
	}
	second, err := json.Marshal(layout)
	if err != nil {
		t.Fatalf("marshal second layout: %v", err)
	}
	if string(first) != string(second) {
		t.Fatalf("overlay merge is not idempotent:\nfirst=%s\nsecond=%s", first, second)
	}

	if len(layout.Tabs) != 7 || layout.Tabs[1] != "Why TiDB & Why Now" {
		t.Fatalf("tabs after merge = %v, want full PingCAP topology", layout.Tabs)
	}
	if len(layout.Sections) != 14 {
		t.Fatalf("sections after merge = %d, want 14", len(layout.Sections))
	}
	if len(layout.HeaderActions) != 1 || layout.HeaderActions[0].Name != "ApplyForPoC" {
		t.Fatalf("headerActions after merge = %+v, want ApplyForPoC appended once", layout.HeaderActions)
	}
}

func TestMergePageLayoutOverlay_MatchesPreCutoverSnapshot(t *testing.T) {
	baselineJSON, err := os.ReadFile(filepath.Clean("../packs/opportunity_page_layout.json"))
	if err != nil {
		t.Fatalf("read baseline opportunity_page_layout.json: %v", err)
	}
	var baseline models.PageLayout
	if err := json.Unmarshal(baselineJSON, &baseline); err != nil {
		t.Fatalf("parse baseline opportunity_page_layout.json: %v", err)
	}

	overlay := mustLoadPageLayoutOverlayByID(t, packs.CuratedOpportunityLayoutID)
	if err := mergePageLayoutOverlay(&baseline, overlay); err != nil {
		t.Fatalf("mergePageLayoutOverlay() error = %v", err)
	}

	wantJSON, err := os.ReadFile(filepath.Join("testdata", "opportunity_layout_pre_cutover_snapshot.json"))
	if err != nil {
		t.Fatalf("read opportunity layout snapshot: %v", err)
	}
	var want models.PageLayout
	if err := json.Unmarshal(wantJSON, &want); err != nil {
		t.Fatalf("parse opportunity layout snapshot: %v", err)
	}

	gotJSON, err := json.Marshal(baseline)
	if err != nil {
		t.Fatalf("marshal merged layout: %v", err)
	}
	normalizedWantJSON, err := json.Marshal(want)
	if err != nil {
		t.Fatalf("marshal expected layout snapshot: %v", err)
	}
	if !bytes.Equal(gotJSON, normalizedWantJSON) {
		t.Fatalf("merged layout drifted from pre-cutover snapshot:\n got=%s\nwant=%s", gotJSON, normalizedWantJSON)
	}
}

func TestMergePageLayoutOverlay_OrderEntry_MatchesPreCleanupSnapshot(t *testing.T) {
	baselineJSON, err := os.ReadFile(filepath.Clean("../../bootstrap/standard_layouts.json"))
	if err != nil {
		t.Fatalf("read baseline standard_layouts.json: %v", err)
	}
	var layouts []models.PageLayout
	if err := json.Unmarshal(baselineJSON, &layouts); err != nil {
		t.Fatalf("parse baseline standard_layouts.json: %v", err)
	}

	var baseline *models.PageLayout
	for i := range layouts {
		if layouts[i].ID == "std_layout_Order" {
			baseline = &layouts[i]
			break
		}
	}
	if baseline == nil {
		t.Fatal("std_layout_Order not found in standard_layouts.json")
	}

	baselineJSONClone, err := json.Marshal(baseline)
	if err != nil {
		t.Fatalf("marshal baseline order layout: %v", err)
	}
	var merged models.PageLayout
	if err := json.Unmarshal(baselineJSONClone, &merged); err != nil {
		t.Fatalf("clone baseline order layout: %v", err)
	}
	overlay := mustLoadPageLayoutOverlayByObject(t, "Order")
	if err := mergePageLayoutOverlay(&merged, overlay); err != nil {
		t.Fatalf("mergePageLayoutOverlay() error = %v", err)
	}

	wantJSON, err := os.ReadFile(filepath.Join("testdata", "order_layout_pre_cleanup_snapshot.json"))
	if err != nil {
		t.Fatalf("read order layout snapshot: %v", err)
	}
	var want models.PageLayout
	if err := json.Unmarshal(wantJSON, &want); err != nil {
		t.Fatalf("parse order layout snapshot: %v", err)
	}

	gotJSON, err := json.Marshal(merged)
	if err != nil {
		t.Fatalf("marshal merged order layout: %v", err)
	}
	normalizedWantJSON, err := json.Marshal(want)
	if err != nil {
		t.Fatalf("marshal expected order layout snapshot: %v", err)
	}
	if !bytes.Equal(gotJSON, normalizedWantJSON) {
		t.Fatalf("merged order layout drifted from pre-cleanup snapshot:\n got=%s\nwant=%s", gotJSON, normalizedWantJSON)
	}
}

func TestMergePageLayoutOverlay_RejectsUnknownSectionTabInReplacement(t *testing.T) {
	layout := models.PageLayout{Tabs: []string{"Details"}}
	err := mergePageLayoutOverlay(&layout, pageLayoutOverlay{
		ObjectAPIName: "Opportunity",
		LayoutID:      packs.CuratedOpportunityLayoutID,
		TabsReplace:   []string{"Details"},
		SectionsReplace: []models.PageSection{
			{
				ID:    "curated_Opportunity_section_why",
				Label: "Why TiDB & Why Now",
				Tab:   stringPtr("Why TiDB & Why Now"),
			},
		},
	})
	if err == nil || err.Error() != `overlay section "curated_Opportunity_section_why" references unknown tab "Why TiDB & Why Now"` {
		t.Fatalf("mergePageLayoutOverlay() error = %v, want unknown-tab failure", err)
	}
}

func TestDecodePageLayoutOverlays_RejectsUnknownFields(t *testing.T) {
	_, err := decodePageLayoutOverlays([]byte(`{"layouts":[{"objectApiName":"Opportunity","layoutId":"curated_Opportunity_default","tabsReplace":["Details"],"tabReplace":["Why TiDB & Why Now"]}]}`))
	if err == nil {
		t.Fatal("decodePageLayoutOverlays() unexpectedly accepted unknown field")
	}
}

func TestPageLayoutOverlaysValidation_RejectsEmptyLayouts(t *testing.T) {
	overlayData, err := decodePageLayoutOverlays([]byte(`{"layouts":[]}`))
	if err != nil {
		t.Fatalf("decodePageLayoutOverlays() error = %v", err)
	}
	err = overlayData.validate()
	if err == nil || err.Error() != `page_layout_overlays.json must contain at least one layout overlay` {
		t.Fatalf("validate() error = %v, want empty-layout rejection", err)
	}
}

func TestPageLayoutOverlaysValidation_RejectsDuplicateImplicitObjectTargets(t *testing.T) {
	overlayData, err := decodePageLayoutOverlays([]byte(`{
		"layouts": [
			{"objectApiName":"Order","sectionsReplace":[{"id":"sec_one","label":"One","columns":1,"fields":["OrderNumber"]}]},
			{"objectApiName":"Order","sectionsReplace":[{"id":"sec_two","label":"Two","columns":1,"fields":["Status"]}]}
		]
	}`))
	if err != nil {
		t.Fatalf("decodePageLayoutOverlays() error = %v", err)
	}
	err = overlayData.validate()
	if err == nil {
		t.Fatal("validate() unexpectedly accepted duplicate implicit object targets")
	}
	if !strings.Contains(err.Error(), `contains duplicate overlay target "object:order"`) {
		t.Fatalf("validate() error = %v, want duplicate implicit target failure", err)
	}
}

func mustLoadPageLayoutOverlayByID(t *testing.T, layoutID string) pageLayoutOverlay {
	t.Helper()
	overlayData, err := decodePageLayoutOverlays(readTestOverlayFile(t, PageLayoutOverlaysFile))
	if err != nil {
		t.Fatalf("parse %s: %v", PageLayoutOverlaysFile, err)
	}
	for _, overlay := range overlayData.Layouts {
		if overlay.LayoutID == layoutID {
			return overlay
		}
	}
	t.Fatalf("layout overlay %q not found in %s", layoutID, PageLayoutOverlaysFile)
	return pageLayoutOverlay{}
}

func mustLoadPageLayoutOverlayByObject(t *testing.T, objectAPIName string) pageLayoutOverlay {
	t.Helper()
	overlayData, err := decodePageLayoutOverlays(readTestOverlayFile(t, PageLayoutOverlaysFile))
	if err != nil {
		t.Fatalf("parse %s: %v", PageLayoutOverlaysFile, err)
	}
	for _, overlay := range overlayData.Layouts {
		if overlay.ObjectAPIName == objectAPIName {
			return overlay
		}
	}
	t.Fatalf("layout overlay for object %q not found in %s", objectAPIName, PageLayoutOverlaysFile)
	return pageLayoutOverlay{}
}

func stringPtr(v string) *string {
	return &v
}
