package runtimeproduction

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"errors"
	"io"
	"reflect"
	"strings"
	"testing"

	featureauth "github.com/nexuscrm/auth"
	kernelauth "github.com/nexuscrm/kernel/pkg/auth"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/sessionv1"
)

func TestSessionDocumentPolicyPreservesRateSubjectDefaults(t *testing.T) {
	t.Setenv("NEXUS_API_HOURLY_LIMIT", "123")
	t.Setenv("NEXUS_API_DAILY_LIMIT", "456")
	for _, test := range []struct {
		name    string
		user    kernelauth.UserSession
		id      string
		metered bool
	}{
		{name: "interactive"},
		{name: "connected app", user: kernelauth.UserSession{ConnectedAppId: "client"}, metered: true},
		{name: "PAT session class", user: kernelauth.UserSession{SessionClass: featureauth.ApiTokenGrantType}, id: "token-1", metered: true},
		{name: "PAT grant", user: kernelauth.UserSession{GrantType: featureauth.ApiTokenGrantType}, id: "token-1", metered: true},
		{name: "PAT without subject", user: kernelauth.UserSession{SessionClass: featureauth.ApiTokenGrantType}},
	} {
		t.Run(test.name, func(t *testing.T) {
			claims := &kernelauth.Claims{User: test.user}
			claims.ID = test.id
			got, err := sessionDocumentPolicyReader(nil)(context.Background(), claims, semantic.DocumentPolicyRequest{})
			if err != nil || got.Restricted {
				t.Fatalf("policy=%+v err=%v", got, err)
			}
			want := &semantic.DocumentPolicy{}
			if test.metered {
				want.HourlyLimit = 123
				want.DailyLimit = 456
			}
			if !reflect.DeepEqual(got, want) {
				t.Fatalf("policy=%+v want=%+v", got, want)
			}
		})
	}
}

// An in-memory driver exercises the existing registry's stored policy parsing,
// owner lookup and route matching without introducing a second policy evaluator.
type documentPolicyDB struct {
	routes string
	fail   bool
}

func (d *documentPolicyDB) Connect(context.Context) (driver.Conn, error) { return d, nil }
func (d *documentPolicyDB) Driver() driver.Driver                        { return d }
func (d *documentPolicyDB) Open(string) (driver.Conn, error)             { return d, nil }
func (d *documentPolicyDB) Close() error                                 { return nil }
func (d *documentPolicyDB) Begin() (driver.Tx, error)                    { return nil, errors.New("unused") }
func (d *documentPolicyDB) Prepare(string) (driver.Stmt, error)          { return nil, errors.New("unused") }
func (d *documentPolicyDB) QueryContext(_ context.Context, query string, _ []driver.NamedValue) (driver.Rows, error) {
	if d.fail {
		return nil, errors.New("database unavailable")
	}
	if strings.Contains(query, "_System_OAuthCustomScope") {
		return &documentPolicyRows{columns: []string{"Name", "CustomPermissionId", "OwnerPack", "IsActive", "AllowedClientTypes", "AllowedMethods", "RoutePatterns", "ResourceLinks"}, values: []driver.Value{"external.read", "", "product", true, "public", "GET", d.routes, "identity records"}}, nil
	}
	if strings.Contains(query, "_System_ConnectedAppDeclaration") {
		return &documentPolicyRows{columns: []string{"ConnectedAppId", "OwnerPack"}, values: []driver.Value{"client", "product"}}, nil
	}
	return nil, errors.New("unexpected query")
}

type documentPolicyRows struct {
	columns []string
	values  []driver.Value
	read    bool
}

func (r *documentPolicyRows) Columns() []string { return r.columns }
func (r *documentPolicyRows) Close() error      { return nil }
func (r *documentPolicyRows) Next(dest []driver.Value) error {
	if r.read {
		return io.EOF
	}
	copy(dest, r.values)
	r.read = true
	return nil
}

func TestSessionDocumentPolicyReusesScopedDocumentRules(t *testing.T) {
	driver := &documentPolicyDB{routes: "/extensions/data/:version/records"}
	db := sql.OpenDB(driver)
	t.Cleanup(func() { _ = db.Close() })
	claims := &kernelauth.Claims{User: kernelauth.UserSession{Scope: "external.read", ConnectedAppId: "client"}}
	request := semantic.DocumentPolicyRequest{RouteFamilyPrefix: "/extensions/data/", LinkRoutes: []semantic.DocumentLinkRoute{{Link: "identity", Method: "GET", Path: "/identity/:userId"}, {Link: "identity", Method: "GET", Path: "/extensions/data/:version/identity/:userId"}}}
	read := sessionDocumentPolicyReader(db)
	got, err := read(context.Background(), claims, request)
	if err != nil || !got.Restricted || !reflect.DeepEqual(got.AllowedLinks, []string{"records"}) {
		t.Fatalf("uncovered identity: %+v %v", got, err)
	}
	driver.routes += " /extensions/data/:version/identity/:userId"
	got, err = read(context.Background(), claims, request)
	if err != nil || !got.Restricted || !reflect.DeepEqual(got.AllowedLinks, []string{"identity", "records"}) {
		t.Fatalf("covered identity: %+v %v", got, err)
	}
	claims.User.ConnectedAppId = "different-owner"
	got, err = read(context.Background(), claims, request)
	if err != nil || got.Restricted || len(got.AllowedLinks) != 0 {
		t.Fatalf("owner mismatch diverged from legacy document rule: %+v %v", got, err)
	}
	driver.fail = true
	if _, err = read(context.Background(), claims, request); err == nil {
		t.Fatal("database failure accepted")
	}
	if _, err = sessionDocumentPolicyReader(nil)(context.Background(), claims, request); err == nil {
		t.Fatal("missing database accepted")
	}
}
