import type { AuthenticatedUser } from '../authorization';

// ---------------------------------------------------------------------------
//  AgentToolContext — trusted server-side principal (NO ability)
// ---------------------------------------------------------------------------

// Module-private brand and trusted set for runtime forgery resistance
const trustedContexts = new WeakSet<AgentToolContext>();
const CONTEXT_BRAND = Symbol('AgentToolContext');

/**
 * Execution context for Agent Tool invocations — branded to prevent
 * forgery. Only {@link AgentToolContextFactory} can create trusted
 * instances; {@link AgentToolExecutor} enforces this at runtime via
 * {@link AgentToolContextFactory.assertTrusted}.
 *
 * All fields come from the trusted server-side authentication layer.
 * The CASL ability is deliberately OMITTED — callers cannot inject a
 * pre-forged ability.
 */
export class AgentToolContext {
  /** The authenticated user's numeric ID. */
  readonly userId!: number;
  /** The authenticated user's login name (for audit). */
  readonly username!: string;
  /**
   * Flat list of `resource:action` permission codes.
   * Frozen at creation — downstream code cannot mutate it.
   */
  readonly permissions!: readonly string[];
  /** Whether the user has a super-admin role. */
  readonly isSuperAdmin!: boolean;

  /** @internal Module-private brand — set only by the Factory. */
  private readonly _brand = CONTEXT_BRAND;

  private constructor() {
    // Construction is only via AgentToolContextFactory
  }
}

/**
 * Creates a trusted {@link AgentToolContext} from the authenticated
 * user record populated by the JWT strategy.
 *
 * This is the ONLY way to create an AgentToolContext — never construct
 * it by hand. The returned context is frozen and registered in an
 * internal WeakSet; {@link assertTrusted} rejects any context not
 * created through this factory.
 */
export class AgentToolContextFactory {
  /**
   * Build a frozen, branded context from an authenticated user.
   *
   * @param user — the user record placed on the request by JWT auth.
   */
  static fromAuthenticatedUser(user: AuthenticatedUser): AgentToolContext {
    const ctx = Object.create(AgentToolContext.prototype) as AgentToolContext;
    Object.defineProperties(ctx, {
      userId: { value: user.id, enumerable: true, writable: false, configurable: false },
      username: { value: user.username, enumerable: true, writable: false, configurable: false },
      permissions: {
        value: Object.freeze([...user.permissions]),
        enumerable: true,
        writable: false,
        configurable: false,
      },
      isSuperAdmin: { value: user.isSuperAdmin, enumerable: true, writable: false, configurable: false },
      _brand: { value: CONTEXT_BRAND, enumerable: false, writable: false, configurable: false },
    });
    Object.freeze(ctx);
    trustedContexts.add(ctx);
    return ctx;
  }

  /**
   * Runtime check: reject forged/plain-object contexts.
   *
   * Called at the entry of {@link AgentToolExecutor.execute} and
   * {@link AgentToolExecutor.listAvailable}. Throws if the argument
   * was not created by {@link fromAuthenticatedUser}.
   */
  static assertTrusted(context: unknown): asserts context is AgentToolContext {
    if (
      !(context instanceof AgentToolContext) ||
      !trustedContexts.has(context)
    ) {
      throw new Error('DENIED: untrusted execution context');
    }
  }
}

// ---------------------------------------------------------------------------
//  ToolDescriptor — public, non-executable tool surface
// ---------------------------------------------------------------------------

/**
 * A read-only descriptor of an agent tool returned to SDK consumers.
 *
 * Does NOT expose `execute`, `validate`, or `requiredPermission` —
 * callers must go through {@link AgentToolExecutor} for double-check
 * authorization, input validation, and audit logging.
 */
export interface ToolDescriptor {
  /** Unique tool name exposed to the LLM (e.g. "search_students"). */
  readonly name: string;
  /** Human-readable description for the model. */
  readonly description: string;
  /**
   * Optional provider-neutral JSON Schema-like input description.
   * Never exposes execution internals or permission details.
   */
  readonly inputSchema?: Record<string, unknown>;
}

// ---------------------------------------------------------------------------
//  ToolDef — internal tool definition (NOT for SDK consumers)
// ---------------------------------------------------------------------------

/**
 * Result of input validation — either success with parsed input,
 * or an error message.
 */
export type ToolInputResult<T> =
  | { readonly ok: true; readonly value: T }
  | { readonly ok: false; readonly error: string };

/**
 * A single Agent Tool definition — internal use only.
 *
 * SDK consumers MUST receive a {@link ToolDescriptor}, never a `ToolDef`.
 * Tool execution always goes through {@link AgentToolExecutor}.
 *
 * @typeParam TInput — the parsed & validated input shape the `execute`
 *   function receives.
 */
export interface ToolDef<TInput = unknown> {
  /** Unique tool name exposed to the LLM (e.g. "search_students"). */
  readonly name: string;
  /** Human-readable description for the model. */
  readonly description: string;
  /**
   * The exact `resource:action` permission code required to use this tool.
   * Checked via {@link AuthorizationService.canPermission}.
   */
  readonly requiredPermission: string;
  /**
   * Optional provider-neutral JSON Schema-like input description.
   */
  readonly inputSchema?: Record<string, unknown>;
  /**
   * Validate and parse raw input from the model.
   * Reject unknown/sensitive fields (userId, permissions, isSuperAdmin, …).
   */
  validate(input: Record<string, unknown>): ToolInputResult<TInput>;
  /**
   * Execute the tool with parsed input and the trusted context.
   * MUST NOT trust `context` to come from input.
   */
  execute(input: TInput, context: AgentToolContext): Promise<unknown>;
}

// ---------------------------------------------------------------------------
//  Tool execution status (for audit)
// ---------------------------------------------------------------------------

export type ToolStatus = 'success' | 'denied' | 'failed' | 'not_found';

/**
 * Result returned by {@link AgentToolExecutor.execute}.
 */
export interface ToolExecutionResult {
  readonly status: ToolStatus;
  readonly toolName: string;
  /** Set on success; `undefined` on denied / failed / not_found. */
  readonly result?: unknown;
  /** Set on denied / failed / not_found; `undefined` on success.
   *  Always a safe, human-readable message — never raw exception text. */
  readonly error?: string;
}
