package metadata

import (
	"context"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/ports"
	domainrls "github.com/nexuscrm/kernel/pkg/rls"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

type listViewOwnerRepoStub struct {
	ports.MetadataRepository

	legacyCalled bool
	filterObject string
	filterSQL    string
	filterRows   []*models.ListView
	schemas      []*models.ObjectMetadata
}

func (r *listViewOwnerRepoStub) GetListViews(ctx context.Context, objectAPIName string) ([]*models.ListView, error) {
	r.legacyCalled = true
	return []*models.ListView{{ID: "legacy", ObjectAPIName: objectAPIName, Label: "Legacy"}}, nil
}

func (r *listViewOwnerRepoStub) GetListViewsWithReadFilter(ctx context.Context, objectAPIName string, readFilterSQL string) ([]*models.ListView, error) {
	r.filterObject = objectAPIName
	r.filterSQL = readFilterSQL
	return r.filterRows, nil
}

func (r *listViewOwnerRepoStub) GetAllSchemas(ctx context.Context) ([]*models.ObjectMetadata, error) {
	return r.schemas, nil
}

func (r *listViewOwnerRepoStub) GetAllRelationships(ctx context.Context) ([]*models.Relationship, error) {
	return nil, nil
}

type listViewRLSProviderStub struct {
	result    *domainrls.Result
	gotSchema *models.ObjectMetadata
	gotObject string
	gotUser   *models.UserSession
	callCount int
}

func (p *listViewRLSProviderStub) BuildRLSResult(ctx context.Context, schema *models.ObjectMetadata, objectName string, user *models.UserSession) *domainrls.Result {
	p.callCount++
	p.gotSchema = schema
	p.gotObject = objectName
	p.gotUser = user
	return p.result
}

func TestMetadataServiceGetListViewsForUserUsesRLSFilterForCallerRows(t *testing.T) {
	ownerID := "005-owner"
	visibility := "Private"
	listViewSchema := ownerVisibilityListViewMetadataSchema()
	repo := &listViewOwnerRepoStub{
		schemas: []*models.ObjectMetadata{listViewSchema},
		filterRows: []*models.ListView{{
			ID:            "lv-owned",
			ObjectAPIName: "Account",
			Label:         "Mine",
			OwnerID:       &ownerID,
			Visibility:    &visibility,
		}},
	}
	service := NewMetadataService(repo, nil, nil)
	provider := &listViewRLSProviderStub{result: &domainrls.Result{
		ObjectAPIName: constants.TableListView,
		OwnerVisibility: &domainrls.OwnerVisibilityFilter{
			UserID:             "005-owner",
			OwnerField:         constants.FieldSysListView_OwnerID,
			VisibilityField:    constants.FieldSysListView_Visibility,
			OwnerVisibleValues: []string{"Private"},
		},
	}}
	service.SetListViewRLSProvider(provider)

	got := service.GetListViewsForUser(context.Background(), "Account", &models.UserSession{ID: "005-owner"})

	if repo.legacyCalled {
		t.Fatal("GetListViewsForUser called legacy catalog reader for an identified user")
	}
	if repo.filterObject != "Account" {
		t.Fatalf("RLS-filtered repo object = %q, want Account", repo.filterObject)
	}
	for _, want := range []string{"OwnerId", "Visibility", "005-owner", "Private"} {
		if !strings.Contains(repo.filterSQL, want) {
			t.Fatalf("RLS-filtered repo SQL missing %q:\n%s", want, repo.filterSQL)
		}
	}
	if provider.callCount != 1 || provider.gotSchema != listViewSchema || provider.gotObject != constants.TableListView {
		t.Fatalf("BuildRLSResult call = count:%d schema:%p object:%q, want count 1 schema %p object %s",
			provider.callCount, provider.gotSchema, provider.gotObject, listViewSchema, constants.TableListView)
	}
	if len(got) != 4 {
		t.Fatalf("GetListViewsForUser returned %d views, want 3 synthetic defaults + 1 caller row", len(got))
	}
	if got[0].ID != "Account-all-view" || got[1].ID != "Account-my-view" || got[2].ID != "Account-recent-view" {
		t.Fatalf("synthetic defaults not prepended: %#v", got[:3])
	}
	if got[3].ID != "lv-owned" || got[3].OwnerID == nil || *got[3].OwnerID != "005-owner" {
		t.Fatalf("caller-owned row not appended: %#v", got[3])
	}
}

func TestMetadataServiceGetListViewsForUserUsesEmptyFilterForAdminBypass(t *testing.T) {
	ownerID := "005-other"
	visibility := "Private"
	repo := &listViewOwnerRepoStub{
		schemas: []*models.ObjectMetadata{ownerVisibilityListViewMetadataSchema()},
		filterRows: []*models.ListView{{
			ID:            "lv-other-private",
			ObjectAPIName: "Account",
			Label:         "Other",
			OwnerID:       &ownerID,
			Visibility:    &visibility,
		}},
	}
	service := NewMetadataService(repo, nil, nil)
	service.SetListViewRLSProvider(&listViewRLSProviderStub{result: &domainrls.Result{
		ObjectAPIName: constants.TableListView,
		Skip:          true,
	}})

	got := service.GetListViewsForUser(context.Background(), "Account", &models.UserSession{ID: "005-admin"})

	if repo.legacyCalled {
		t.Fatal("admin/all-record list-view catalog read fell back to legacy reader")
	}
	if repo.filterObject != "Account" || repo.filterSQL != "" {
		t.Fatalf("admin/all-record repo filter = object:%q sql:%q, want Account with empty filter", repo.filterObject, repo.filterSQL)
	}
	if len(got) != 4 || got[3].ID != "lv-other-private" {
		t.Fatalf("admin/all-record catalog views = %#v, want synthetic defaults plus all DB rows", got)
	}
}

func TestMetadataServiceGetListViewsForUserWithoutUserKeepsLegacyCatalogRead(t *testing.T) {
	repo := &listViewOwnerRepoStub{}
	service := NewMetadataService(repo, nil, nil)

	got := service.GetListViewsForUser(context.Background(), "Account", nil)

	if !repo.legacyCalled {
		t.Fatal("GetListViewsForUser without user did not use the legacy no-user catalog reader")
	}
	if repo.filterObject != "" {
		t.Fatalf("GetListViewsForUser without user called RLS-filtered repo for object %q", repo.filterObject)
	}
	if len(got) != 4 {
		t.Fatalf("GetListViewsForUser without user returned %d views, want 3 defaults + 1 legacy row", len(got))
	}
	if got[3].ID != "legacy" {
		t.Fatalf("legacy row not appended after defaults: %#v", got)
	}
}

func ownerVisibilityListViewMetadataSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName:      constants.TableListView,
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: &models.ObjectAccessPolicy{RowRead: &models.RowReadPolicy{
			Kind:               "ownerVisibility",
			OwnerField:         constants.FieldSysListView_OwnerID,
			VisibilityField:    constants.FieldSysListView_Visibility,
			OwnerVisibleValues: []string{"Private"},
		}},
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, IsSystem: true},
			{APIName: constants.FieldSysListView_OwnerID},
			{APIName: constants.FieldSysListView_Visibility},
		},
	}
}
