package runtimeapi

import (
	"context"
	"database/sql"
	"errors"
	"strconv"
	"time"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

const (
	mailboxAuthorizationCleanupBatch    = 256
	mailboxAuthorizationMaxCleanupBatch = 10_000
)

// SQLMailboxAuthorizationHandleStore keeps only a digest of the opaque
// callback handle. The row is Runtime-owned and bound to the exact delegated
// session, Permission State floor, App installation, Catalog revision, and
// security epoch.
type mailboxAuthorizationSQLExecutor interface {
	ExecContext(context.Context, string, ...any) (sql.Result, error)
	QueryRowContext(context.Context, string, ...any) *sql.Row
}

type mailboxAuthorizationTransactionOwner interface {
	RunInPessimisticTransaction(context.Context, func(context.Context) error) error
	Executor(context.Context) (mailboxAuthorizationSQLExecutor, bool)
}

type SQLMailboxAuthorizationHandleStore struct {
	db           *sql.DB
	transactions mailboxAuthorizationTransactionOwner
}

func NewSQLMailboxAuthorizationHandleStore(
	db *sql.DB,
	transactions mailboxAuthorizationTransactionOwner,
) (*SQLMailboxAuthorizationHandleStore, error) {
	if db == nil || transactions == nil {
		return nil, errMailboxAuthorizationUnavailable
	}
	return &SQLMailboxAuthorizationHandleStore{db: db, transactions: transactions}, nil
}

func (store *SQLMailboxAuthorizationHandleStore) VerifySchema(ctx context.Context) error {
	if store == nil || store.db == nil || ctx == nil {
		return errMailboxAuthorizationUnavailable
	}
	rows, err := store.db.QueryContext(ctx, `
SELECT HandleDigest, OrgId, AppInstallationId, AuthorizedSubjectId, SessionId,
       AuthEpoch, PermissionStateLineage, PermissionStateRevision,
       CatalogRevision, SecurityEpoch, ExpiresAt, ConsumedAt,
       CreatedDate, LastModifiedDate
  FROM _System_RuntimeMailboxAuthorizationHandle
 LIMIT 0`)
	if err != nil {
		return err
	}
	return rows.Close()
}

func (store *SQLMailboxAuthorizationHandleStore) Mint(
	ctx context.Context,
	record mailboxAuthorizationHandle,
	ttl time.Duration,
) error {
	if store == nil || store.db == nil || !validMailboxAuthorizationHandleBinding(record) ||
		record.ConsumedAt != nil || ttl <= 0 || ttl%time.Second != 0 {
		return errMailboxAuthorizationUnavailable
	}
	if _, err := store.CleanupExpired(ctx, mailboxAuthorizationCleanupBatch); err != nil {
		return err
	}
	_, err := store.db.ExecContext(ctx, `
INSERT INTO _System_RuntimeMailboxAuthorizationHandle
 (HandleDigest, OrgId, AppInstallationId, AuthorizedSubjectId, SessionId,
  AuthEpoch, PermissionStateLineage, PermissionStateRevision,
  CatalogRevision, SecurityEpoch, ExpiresAt, ConsumedAt,
  CreatedDate, LastModifiedDate)
SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
        TIMESTAMPADD(SECOND, ?, UTC_TIMESTAMP(6)), NULL,
        UTC_TIMESTAMP(6), UTC_TIMESTAMP(6)`,
		record.HandleDigest, record.OrgID, record.AppInstallationID,
		record.AuthorizedSubjectID, record.SessionID, record.AuthEpoch,
		record.PermissionFloor.Lineage().Bytes(), record.PermissionFloor.Revision(),
		record.CatalogRevision, record.SecurityEpoch, int64(ttl/time.Second),
	)
	return err
}

// CleanupExpired removes one deterministic, bounded batch using the database
// clock and the expiry index. Consumed handles remain replayable until their
// original expiry and are then removed through the same path.
func (store *SQLMailboxAuthorizationHandleStore) CleanupExpired(
	ctx context.Context,
	batchSize int,
) (int64, error) {
	if store == nil || store.db == nil || ctx == nil ||
		batchSize <= 0 || batchSize > mailboxAuthorizationMaxCleanupBatch {
		return 0, errMailboxAuthorizationUnavailable
	}
	result, err := store.db.ExecContext(ctx, `
DELETE FROM _System_RuntimeMailboxAuthorizationHandle
 WHERE ExpiresAt <= UTC_TIMESTAMP(6)
 ORDER BY ExpiresAt, HandleDigest
 LIMIT ?`, batchSize)
	if err != nil {
		return 0, err
	}
	return result.RowsAffected()
}

// ConsumeAuthorized is exact-replay idempotent. It locks and re-reads the
// handle, runs the current-session and Permission State authorization fence,
// and records first consumption in one transaction. Exact replays repeat the
// authorization fence without changing the original completion time.
func (store *SQLMailboxAuthorizationHandleStore) ConsumeAuthorized(
	ctx context.Context,
	digest string,
	authorize func(context.Context, mailboxAuthorizationHandle) error,
) (bool, error) {
	if store == nil || store.db == nil || store.transactions == nil || authorize == nil {
		return false, errMailboxAuthorizationUnavailable
	}
	if len(digest) != 64 {
		return false, errMailboxAuthorizationDenied
	}
	duplicate := false
	err := store.transactions.RunInPessimisticTransaction(ctx, func(txCtx context.Context) error {
		executor, ok := store.transactions.Executor(txCtx)
		if !ok || executor == nil {
			return errMailboxAuthorizationUnavailable
		}
		record, err := scanMailboxAuthorizationHandle(executor.QueryRowContext(
			txCtx,
			mailboxAuthorizationHandleSelect+`
 WHERE BINARY HandleDigest = BINARY ?
   AND ExpiresAt > UTC_TIMESTAMP(6)
 LIMIT 1 FOR UPDATE`,
			digest,
		))
		if errors.Is(err, sql.ErrNoRows) {
			return errMailboxAuthorizationDenied
		}
		if err != nil {
			return err
		}
		if !validMailboxAuthorizationHandle(record) {
			return errMailboxAuthorizationDenied
		}
		if err := authorize(txCtx, record); err != nil {
			return err
		}
		if record.ConsumedAt != nil {
			duplicate = true
			return nil
		}
		result, err := executor.ExecContext(txCtx, `
UPDATE _System_RuntimeMailboxAuthorizationHandle
   SET ConsumedAt = UTC_TIMESTAMP(6), LastModifiedDate = UTC_TIMESTAMP(6)
 WHERE BINARY HandleDigest = BINARY ?
   AND ConsumedAt IS NULL
   AND ExpiresAt > UTC_TIMESTAMP(6)`,
			digest,
		)
		if err != nil {
			return err
		}
		rows, err := result.RowsAffected()
		if err != nil {
			return err
		}
		if rows != 1 {
			return errMailboxAuthorizationDenied
		}
		return nil
	})
	return duplicate, err
}

const mailboxAuthorizationHandleSelect = `
SELECT HandleDigest, OrgId, AppInstallationId, AuthorizedSubjectId, SessionId,
       AuthEpoch, PermissionStateLineage, CAST(PermissionStateRevision AS CHAR),
       CatalogRevision, SecurityEpoch, ExpiresAt, ConsumedAt, CreatedDate
  FROM _System_RuntimeMailboxAuthorizationHandle`

type mailboxAuthorizationRow interface{ Scan(...any) error }

func scanMailboxAuthorizationHandle(
	row mailboxAuthorizationRow,
) (mailboxAuthorizationHandle, error) {
	var record mailboxAuthorizationHandle
	var consumed sql.NullTime
	var permissionLineage []byte
	var permissionRevision string
	if err := row.Scan(
		&record.HandleDigest, &record.OrgID, &record.AppInstallationID,
		&record.AuthorizedSubjectID, &record.SessionID, &record.AuthEpoch,
		&permissionLineage, &permissionRevision,
		&record.CatalogRevision, &record.SecurityEpoch, &record.ExpiresAt,
		&consumed, &record.CreatedAt,
	); err != nil {
		return mailboxAuthorizationHandle{}, err
	}
	lineage, err := permissionstatev1.LineageFromBytes(permissionLineage)
	if err != nil {
		return mailboxAuthorizationHandle{}, err
	}
	revision, err := strconv.ParseUint(permissionRevision, 10, 64)
	if err != nil {
		return mailboxAuthorizationHandle{}, err
	}
	record.PermissionFloor, err = permissionstatev1.NewVersion(lineage, revision)
	if err != nil {
		return mailboxAuthorizationHandle{}, err
	}
	if consumed.Valid {
		value := consumed.Time
		record.ConsumedAt = &value
	}
	return record, nil
}

func validMailboxAuthorizationHandle(record mailboxAuthorizationHandle) bool {
	return validMailboxAuthorizationHandleBinding(record) &&
		!record.ExpiresAt.IsZero() && !record.CreatedAt.IsZero()
}

func validMailboxAuthorizationHandleBinding(record mailboxAuthorizationHandle) bool {
	return len(record.HandleDigest) == 64 && record.OrgID != "" &&
		record.AppInstallationID != "" && record.AuthorizedSubjectID != "" &&
		record.SessionID != "" && record.AuthEpoch > 0 &&
		record.PermissionFloor.Validate() == nil && record.CatalogRevision > 0 &&
		record.SecurityEpoch > 0
}

type effectiveSnapshotLoader interface {
	LoadCurrentEffectivePermissionSnapshot(
		context.Context,
		permissionstatev1.Subject,
	) (permissionstatev1.EffectiveSnapshot, error)
}

type effectivePermissionFloorFencer interface {
	FencePermissionFloorWithinTransaction(context.Context, permissionstatev1.Version) error
}

type currentMailboxPermissionAuthorizer struct {
	loader effectiveSnapshotLoader
	fencer effectivePermissionFloorFencer
}

func newCurrentMailboxPermissionAuthorizer(
	loader effectiveSnapshotLoader,
	fencer effectivePermissionFloorFencer,
) (*currentMailboxPermissionAuthorizer, error) {
	if loader == nil || fencer == nil {
		return nil, errMailboxAuthorizationUnavailable
	}
	return &currentMailboxPermissionAuthorizer{loader: loader, fencer: fencer}, nil
}

func (authorizer *currentMailboxPermissionAuthorizer) AuthorizeMailboxSubjectWithinTransaction(
	ctx context.Context,
	subject permissionstatev1.Subject,
	floor permissionstatev1.Version,
	requiredPermission MailboxPermissionRequirement,
) error {
	if authorizer == nil || authorizer.fencer == nil {
		return errMailboxAuthorizationUnavailable
	}
	if err := authorizer.AuthorizeMailboxSubject(
		ctx,
		subject,
		floor,
		requiredPermission,
	); err != nil {
		return err
	}
	if err := authorizer.fencer.FencePermissionFloorWithinTransaction(ctx, floor); err != nil {
		return errors.Join(errMailboxAuthorizationUnavailable, err)
	}
	return nil
}

func (authorizer *currentMailboxPermissionAuthorizer) AuthorizeMailboxSubject(
	ctx context.Context,
	subject permissionstatev1.Subject,
	floor permissionstatev1.Version,
	requiredPermission MailboxPermissionRequirement,
) error {
	if authorizer == nil || authorizer.loader == nil || subject.Validate() != nil ||
		floor.Validate() != nil || !requiredPermission.valid() {
		return errMailboxAuthorizationDenied
	}
	snapshot, err := authorizer.loader.LoadCurrentEffectivePermissionSnapshot(ctx, subject)
	if err != nil || snapshot.Validate() != nil {
		return errors.Join(errMailboxAuthorizationUnavailable, err)
	}
	comparison, err := snapshot.Version().Compare(floor)
	if err != nil || comparison != 0 {
		return errors.Join(errMailboxAuthorizationUnavailable, err)
	}
	if (requiredPermission.CustomPermission != "" && !snapshot.HasCustomPermission(requiredPermission.CustomPermission)) ||
		(requiredPermission.SystemPermission != "" && !snapshot.HasSystemPermission(requiredPermission.SystemPermission)) {
		return errMailboxAuthorizationDenied
	}
	return nil
}

func (admission *SecurityAdmission) ReadMailboxSession(
	ctx context.Context,
	sessionID string,
	userID string,
) (mailboxAuthorizationSession, error) {
	if admission == nil || admission.sessions == nil || sessionID == "" || userID == "" {
		return mailboxAuthorizationSession{}, errMailboxAuthorizationDenied
	}
	session, err := admission.sessions(ctx, sessionID, userID)
	if err != nil {
		return mailboxAuthorizationSession{}, errors.Join(errMailboxAuthorizationUnavailable, err)
	}
	if session == nil || session.AuthEpoch == 0 || session.User.ID != userID ||
		session.Subject.Validate() != nil ||
		session.PermissionFloor.Validate() != nil ||
		session.Subject.UserID() != userID ||
		session.Subject.ProfileID() != session.User.ProfileID {
		return mailboxAuthorizationSession{}, errMailboxAuthorizationDenied
	}
	return mailboxAuthorizationSession{
		User: session.User, Subject: session.Subject, AuthEpoch: session.AuthEpoch,
		PermissionFloor: session.PermissionFloor,
	}, nil
}

var _ mailboxAuthorizationHandleStore = (*SQLMailboxAuthorizationHandleStore)(nil)
var _ mailboxPermissionAuthorizer = (*currentMailboxPermissionAuthorizer)(nil)
var _ mailboxSessionReader = (*SecurityAdmission)(nil)
