import Fastify, { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import tierGuardPlugin from '../tier-guard.plugin';

const createReply = (): Partial<FastifyReply> & { payload?: unknown; statusCode?: number } => {
  return {
    sent: false,
    code: jest.fn(function(this: any, status: number) {
      this.statusCode = status;
      return this;
    }),
    send: jest.fn(function(this: any, payload: unknown) {
      this.payload = payload;
      this.sent = true;
      return this;
    }),
  };
};

describe('tier guard plugin', () => {
  let fastify: FastifyInstance;
  let authenticateMock: jest.Mock;

  beforeEach(async () => {
    fastify = Fastify();

    // Mock authenticate to set userContext
    authenticateMock = jest.fn(async (request: FastifyRequest) => {
      request.userContext = {
        userId: 'auth0|user123',
        email: 'user@example.com',
        emailVerified: true,
        onboardingCompleted: true,
        isAdmin: false,
        subscriptionTier: 'free',
      };
    });
    fastify.decorate('authenticate', authenticateMock);

    await fastify.register(tierGuardPlugin);
  });

  afterEach(async () => {
    await fastify.close();
    jest.clearAllMocks();
  });

  describe('requireTier with minTier', () => {
    it('allows access when user tier meets minimum', async () => {
      authenticateMock.mockImplementation(async (request: FastifyRequest) => {
        request.userContext = {
          userId: 'auth0|user123',
          email: 'user@example.com',
          emailVerified: true,
          onboardingCompleted: true,
          isAdmin: false,
          subscriptionTier: 'pro',
        };
      });

      const request = {} as FastifyRequest;
      const reply = createReply();

      const handler = fastify.requireTier({ minTier: 'pro' });
      await handler(request, reply as FastifyReply);

      expect(authenticateMock).toHaveBeenCalledTimes(1);
      expect(reply.code).not.toHaveBeenCalled();
      expect(reply.send).not.toHaveBeenCalled();
    });

    it('allows access when user tier exceeds minimum', async () => {
      authenticateMock.mockImplementation(async (request: FastifyRequest) => {
        request.userContext = {
          userId: 'auth0|user123',
          email: 'user@example.com',
          emailVerified: true,
          onboardingCompleted: true,
          isAdmin: false,
          subscriptionTier: 'enterprise',
        };
      });

      const request = {} as FastifyRequest;
      const reply = createReply();

      const handler = fastify.requireTier({ minTier: 'pro' });
      await handler(request, reply as FastifyReply);

      expect(reply.code).not.toHaveBeenCalled();
    });

    it('denies access when user tier is below minimum', async () => {
      const request = {} as FastifyRequest;
      const reply = createReply();

      const handler = fastify.requireTier({ minTier: 'pro' });
      await handler(request, reply as FastifyReply);

      expect(reply.code).toHaveBeenCalledWith(403);
      expect(reply.send).toHaveBeenCalledWith(
        expect.objectContaining({
          error: 'TIER_REQUIRED',
          requiredTier: 'pro',
          currentTier: 'free',
        })
      );
    });
  });

  describe('requireTier with featureKey', () => {
    it('denies free tier access to pro feature', async () => {
      const request = {} as FastifyRequest;
      const reply = createReply();

      const handler = fastify.requireTier({ featureKey: 'document.scanMaintenanceSchedule' });
      await handler(request, reply as FastifyReply);

      expect(reply.code).toHaveBeenCalledWith(403);
      expect(reply.send).toHaveBeenCalledWith(
        expect.objectContaining({
          error: 'TIER_REQUIRED',
          requiredTier: 'pro',
          currentTier: 'free',
          feature: 'document.scanMaintenanceSchedule',
          featureName: 'Scan for Maintenance Schedule',
        })
      );
    });

    it('allows pro tier access to pro feature', async () => {
      authenticateMock.mockImplementation(async (request: FastifyRequest) => {
        request.userContext = {
          userId: 'auth0|user123',
          email: 'user@example.com',
          emailVerified: true,
          onboardingCompleted: true,
          isAdmin: false,
          subscriptionTier: 'pro',
        };
      });

      const request = {} as FastifyRequest;
      const reply = createReply();

      const handler = fastify.requireTier({ featureKey: 'document.scanMaintenanceSchedule' });
      await handler(request, reply as FastifyReply);

      expect(reply.code).not.toHaveBeenCalled();
    });

    it('allows access for unknown feature (fail open)', async () => {
      const request = {} as FastifyRequest;
      const reply = createReply();

      const handler = fastify.requireTier({ featureKey: 'unknown.feature' });
      await handler(request, reply as FastifyReply);

      expect(reply.code).not.toHaveBeenCalled();
    });
  });

  describe('error handling', () => {
    it('returns 500 when authenticate handler is not a function', async () => {
      const brokenFastify = Fastify();
      // Decorate with a non-function value to simulate missing handler
      brokenFastify.decorate('authenticate', 'not-a-function' as any);
      await brokenFastify.register(tierGuardPlugin);

      const request = {} as FastifyRequest;
      const reply = createReply();

      const handler = brokenFastify.requireTier({ minTier: 'pro' });
      await handler(request, reply as FastifyReply);

      expect(reply.code).toHaveBeenCalledWith(500);
      expect(reply.send).toHaveBeenCalledWith(
        expect.objectContaining({
          error: 'Internal server error',
          message: 'Authentication handler missing',
        })
      );

      await brokenFastify.close();
    });

    it('defaults to free tier when userContext is missing', async () => {
      authenticateMock.mockImplementation(async () => {
        // Don't set userContext
      });

      const request = {} as FastifyRequest;
      const reply = createReply();

      const handler = fastify.requireTier({ minTier: 'pro' });
      await handler(request, reply as FastifyReply);

      expect(reply.code).toHaveBeenCalledWith(403);
      expect(reply.send).toHaveBeenCalledWith(
        expect.objectContaining({
          currentTier: 'free',
        })
      );
    });
  });
});
