import { describe, it, expect } from "vitest";
import { ExecutionContext, UnauthorizedException } from "@nestjs/common";
import { ROUTE_ARGS_METADATA } from "@nestjs/common/constants";
import { CurrentUser } from "./current-user.decorator";
import type { AuthUser } from "@mosaic/shared";

/**
 * Extract the factory function from a NestJS param decorator created with createParamDecorator.
 * NestJS stores param decorator factories in metadata on a dummy class.
 */
function getParamDecoratorFactory(): (data: unknown, ctx: ExecutionContext) => AuthUser {
  class TestController {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    testMethod(@CurrentUser() _user: AuthUser): void {
      // no-op
    }
  }

  const metadata = Reflect.getMetadata(ROUTE_ARGS_METADATA, TestController, "testMethod");

  // The metadata keys are in the format "paramtype:index"
  const key = Object.keys(metadata)[0];
  return metadata[key].factory;
}

function createMockExecutionContext(user?: AuthUser): ExecutionContext {
  const mockRequest = {
    ...(user !== undefined ? { user } : {}),
  };

  return {
    switchToHttp: () => ({
      getRequest: () => mockRequest,
    }),
  } as ExecutionContext;
}

describe("CurrentUser decorator", () => {
  const factory = getParamDecoratorFactory();

  const mockUser: AuthUser = {
    id: "user-123",
    email: "test@example.com",
    name: "Test User",
  };

  it("should return the user when present on the request", () => {
    const ctx = createMockExecutionContext(mockUser);
    const result = factory(undefined, ctx);

    expect(result).toEqual(mockUser);
  });

  it("should return the user with optional fields", () => {
    const userWithOptionalFields: AuthUser = {
      ...mockUser,
      image: "https://example.com/avatar.png",
      workspaceId: "ws-123",
      workspaceRole: "owner",
    };

    const ctx = createMockExecutionContext(userWithOptionalFields);
    const result = factory(undefined, ctx);

    expect(result).toEqual(userWithOptionalFields);
    expect(result.image).toBe("https://example.com/avatar.png");
    expect(result.workspaceId).toBe("ws-123");
  });

  it("should throw UnauthorizedException when user is undefined", () => {
    const ctx = createMockExecutionContext(undefined);

    expect(() => factory(undefined, ctx)).toThrow(UnauthorizedException);
    expect(() => factory(undefined, ctx)).toThrow("No authenticated user found on request");
  });

  it("should throw UnauthorizedException when request has no user property", () => {
    // Request object without a user property at all
    const ctx = {
      switchToHttp: () => ({
        getRequest: () => ({}),
      }),
    } as ExecutionContext;

    expect(() => factory(undefined, ctx)).toThrow(UnauthorizedException);
  });

  it("should ignore the data parameter", () => {
    const ctx = createMockExecutionContext(mockUser);

    // The decorator doesn't use the data parameter, but ensure it doesn't break
    const result = factory("some-data", ctx);

    expect(result).toEqual(mockUser);
  });
});
