package control

import (
	"context"
	"encoding/json"
	"net/http/httptest"
	"slices"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
)

func TestFinanceCRMScopeOnlyRequiredTables(t *testing.T) {
	want := []string{"Account", "Org__c", "Order", "OrderItem", "User", "RecordType", "Product2", "Quote", "Opportunity"}
	if !slices.Equal(financeCRMTables(true), want) {
		t.Fatal("finance prerequisite observation scope changed without contract update")
	}
	for _, scope := range financeCRMTableScope() {
		if len(scope.Consumers) == 0 || scope.FinalTable == "" {
			t.Fatal("unbound table admitted")
		}
	}
	old := CollectionState{Cursor: "old-four-table-cursor", ObservedAt: time.Now()}
	if c := crmScopeCheckpoint(old); c.Cursor != "" || !c.ObservedAt.IsZero() {
		t.Fatal("expanded references would be skipped by old checkpoint")
	}
	old.ScopeVersion = financeCRMScopeVersion
	if c := crmScopeCheckpoint(old); c.Cursor != old.Cursor || c.ObservedAt != old.ObservedAt {
		t.Fatal("current scope loses pagination")
	}
}

func TestSharedCRMAlertKeepsFailureButDoesNotClaimFinanceImpact(t *testing.T) {
	o := testOperations(t)
	b := testBatch("failed", time.Now().UTC())
	b.Runs[0].Task, b.Runs[0].Provider, b.State.Provider = "crm_extract", "crm_extract", "crm_extract"
	if err := o.CommitBatch(context.Background(), b); err != nil {
		t.Fatal(err)
	}
	r := gin.New()
	o.Register(r, nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, httptest.NewRequest("GET", "/api/v1/alerts", nil))
	var payload struct {
		Items []struct {
			Status string `json:"status"`
			Impact string `json:"impact_scope"`
		} `json:"items"`
	}
	if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &payload) != nil || len(payload.Items) != 1 {
		t.Fatal("alert projection unavailable", w.Code)
	}
	if payload.Items[0].Status != "open" || payload.Items[0].Impact != "shared_job_unverified" {
		t.Fatal("failure hidden or finance impact fabricated")
	}
}

func TestFinanceCRMTableProjectionDoesNotBorrowOtherTableSuccess(t *testing.T) {
	o := testOperations(t)
	now := time.Now().UTC()
	for i, item := range []struct{ table, status string }{
		{"nexuscrm_final.nexuscrm_account", "failed"},
		{"nexuscrm_final.nexuscrm_order", "succeeded"},
		{"nexuscrm_final.nexuscrm_case", "failed"},
		{"nexuscrm_final.nexuscrm_account_extra", "succeeded"},
	} {
		b := testBatch(item.status, now.Add(time.Duration(i)*time.Minute))
		b.Runs[0].Provider, b.Runs[0].Task = "crm_load", "crm_load"
		b.Runs[0].ExternalID = item.table
		b.Runs[0].OutputRefs = []string{item.table}
		b.State.Provider = "crm_load"
		if err := o.CommitBatch(context.Background(), b); err != nil {
			t.Fatal(err)
		}
	}
	views, err := o.crmTableViews(context.Background())
	if err != nil || len(views) != 9 {
		t.Fatal(err, len(views))
	}
	for _, v := range views {
		switch v.SourceTable {
		case "Account":
			if v.Status != "failed" || v.LatestRun == nil {
				t.Fatal("Account borrowed another table result")
			}
		case "Order":
			if v.Status != "succeeded" {
				t.Fatal("Order status overwritten")
			}
		default:
			if v.Status != "unknown" || v.LatestRun != nil {
				t.Fatal("missing table falsely observed", v.SourceTable)
			}
		}
	}
	b := testBatch("failed", now)
	b.Runs[0].Task, b.Runs[0].Provider, b.State.Provider = "crm_extract", "crm_extract", "crm_extract"
	if err := o.CommitBatch(context.Background(), b); err != nil {
		t.Fatal(err)
	}
	tasks, err := o.TaskViews(context.Background(), now)
	if err != nil {
		t.Fatal(err)
	}
	for _, task := range tasks {
		if task.Key == "crm_extract" && (task.LatestRun != nil || task.LatestJobRun == nil || task.LatestJobRun.Status != "failed") {
			t.Fatal("unscoped shared job misattributed to finance subset")
		}
		if task.Key == "crm_load" && len(task.CRMTables) != 9 {
			t.Fatal("table projection missing")
		}
	}
}
