import { INestApplication, ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { io, Socket } from 'socket.io-client';
import { AppModule } from '../src/app.module';

Object.assign(process.env, {
  NODE_ENV: 'test',
  EMAIL_ENABLED: 'false',
  REDIS_ENABLED: 'false',
  REDIS_SOCKET_ADAPTER_ENABLED: 'false',
  QUEUE_ENABLED: 'false',
  REQUEST_LOGGING_ENABLED: 'false',
  FEED_CACHE_ENABLED: 'false',
  BCRYPT_SALT_ROUNDS: '8',
  PUBLIC_BASE_URL: process.env.PUBLIC_BASE_URL ?? 'http://127.0.0.1:4000',
  STORAGE_PUBLIC_BASE_URL:
    process.env.STORAGE_PUBLIC_BASE_URL ?? process.env.PUBLIC_BASE_URL ?? 'http://127.0.0.1:4000',
  MONGODB_URI: process.env.MONGODB_URI ?? 'mongodb://127.0.0.1:27017/oudelaa-e2e',
  JWT_ACCESS_SECRET: process.env.JWT_ACCESS_SECRET ?? 'test-access-secret-123456',
  JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET ?? 'test-refresh-secret-123456',
  SUPERADMIN_EMAIL: process.env.SUPERADMIN_EMAIL ?? 'superadmin@example.com',
  SUPERADMIN_PASSWORD: process.env.SUPERADMIN_PASSWORD ?? 'StrongPass123!',
  SUPERADMIN_ACCESS_SECRET:
    process.env.SUPERADMIN_ACCESS_SECRET ?? 'test-superadmin-access-123456',
  SUPERADMIN_REFRESH_SECRET:
    process.env.SUPERADMIN_REFRESH_SECRET ?? 'test-superadmin-refresh-123456',
});

jest.setTimeout(120000);

const tinyPngBuffer = Buffer.from(
  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9WlH0i8AAAAASUVORK5CYII=',
  'base64',
);

describe('Oudelaa smoke (e2e)', () => {
  let app: INestApplication;

  const ts = Date.now();
  const primary = {
    email: `smoke_primary_${ts}@example.com`,
    password: 'StrongPass123!',
    accessToken: '',
    userId: '',
    username: '',
  };
  const secondary = {
    email: `smoke_secondary_${ts}@example.com`,
    password: 'StrongPass123!',
    accessToken: '',
    userId: '',
    username: '',
  };
  const tertiary = {
    email: `smoke_tertiary_${ts}@example.com`,
    password: 'StrongPass123!',
    accessToken: '',
    userId: '',
    username: '',
  };
  const superAdmin = {
    accessToken: '',
    refreshToken: '',
    secondAccessToken: '',
    secondRefreshToken: '',
  };
  let postId = '';
  let editableCommentId = '';

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    const configService = app.get(ConfigService);
    app.setGlobalPrefix(configService.get<string>('globalPrefix', 'api/v1'));
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
        transformOptions: { enableImplicitConversion: true },
      }),
    );
    await app.listen(0, '127.0.0.1');
  });

  afterAll(async () => {
    if (app) {
      await app.close();
    }
  });

  const registerAndVerify = async (user: {
    email: string;
    password: string;
    accessToken: string;
    userId: string;
    username: string;
  }) => {
    const registerResponse = await request(app.getHttpServer())
      .post('/api/v1/auth/register-basic')
      .send({
        email: user.email,
        password: user.password,
        confirmPassword: user.password,
      })
      .expect(201);

    await request(app.getHttpServer())
      .post('/api/v1/auth/verify-email')
      .send({
        email: user.email,
        code: registerResponse.body.debugCode,
      })
      .expect(200);

    const loginResponse = await request(app.getHttpServer())
      .post('/api/v1/auth/login')
      .send({ email: user.email, password: user.password })
      .expect(200);

    user.accessToken = loginResponse.body.accessToken;
    user.userId = loginResponse.body.user._id || loginResponse.body.user.id;
    user.username = loginResponse.body.user.username;
  };

  it('/api/v1/health (GET)', () => {
    return request(app.getHttpServer()).get('/api/v1/health').expect(200);
  });

  it('/api/v1/health/ready validates MongoDB and storage', async () => {
    const response = await request(app.getHttpServer()).get('/api/v1/health/ready').expect(200);
    expect(response.body).toEqual(expect.objectContaining({
      status: 'ready',
      checks: expect.objectContaining({
        mongodb: expect.objectContaining({ status: 'up' }),
        storage: expect.objectContaining({ status: 'up' }),
      }),
    }));
  });

  it('registers and verifies smoke users', async () => {
    await registerAndVerify(primary);
    await registerAndVerify(secondary);
    await registerAndVerify(tertiary);

    expect(primary.accessToken).toBeTruthy();
    expect(secondary.username).toBeTruthy();
    expect(tertiary.userId).toBeTruthy();
  });

  it('updates profile setup with avatar upload', async () => {
    const response = await request(app.getHttpServer())
      .patch('/api/v1/users/me/profile-setup')
      .set('Authorization', `Bearer ${primary.accessToken}`)
      .field('stageName', 'Smoke Artist')
      .field('bio', 'Smoke profile bio')
      .field('location', 'Riyadh, Saudi Arabia')
      .field('latitude', '24.7136')
      .field('longitude', '46.6753')
      .attach('avatarFile', tinyPngBuffer, { filename: 'avatar.png', contentType: 'image/png' })
      .expect(200);

    expect(response.body.stageName).toBe('Smoke Artist');
    expect(response.body.avatar).toMatch(/^https?:\/\//);
  });

  it('updates music setup and discovers talents', async () => {
    await request(app.getHttpServer())
      .patch('/api/v1/users/me/music-setup')
      .set('Authorization', `Bearer ${primary.accessToken}`)
      .send({
        musicRoles: ['instrumentalist', 'composer'],
        musicGenres: ['Tarab'],
        experienceLevel: 'intermediate',
        favoriteInstruments: ['Oud'],
        favoriteMaqamat: ['Rast'],
      })
      .expect(200);

    const discoverResponse = await request(app.getHttpServer())
      .get('/api/v1/users/discover?musicRole=instrumentalist&hasAvatarOnly=true&limit=8')
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .expect(200);

    expect(discoverResponse.body.items).toBeInstanceOf(Array);
    expect(discoverResponse.body.roleBuckets).toBeInstanceOf(Array);
    expect(discoverResponse.body.pagination).toBeTruthy();

    const overviewResponse = await request(app.getHttpServer())
      .get(`/api/v1/users/${primary.userId}/profile-overview`)
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .expect(200);

    expect(overviewResponse.body.stats).toBeTruthy();
    expect(overviewResponse.body.contentCounts).toBeTruthy();
    expect(overviewResponse.body.viewerState).toBeTruthy();
  });

  it('creates an image post with tag and mention', async () => {
    const response = await request(app.getHttpServer())
      .post('/api/v1/posts')
      .set('Authorization', `Bearer ${primary.accessToken}`)
      .field('content', `Smoke image post with @${secondary.username} #smoke`)
      .field('visibility', 'public')
      .field('taggedUserIds', secondary.userId)
      .field('mentionUsernames', secondary.username)
      .field('location', 'Riyadh, Saudi Arabia')
      .field('latitude', '24.7136')
      .field('longitude', '46.6753')
      .attach('imageFiles', tinyPngBuffer, { filename: 'post.png', contentType: 'image/png' })
      .expect(201);

    postId = response.body._id || response.body.id;

    expect(postId).toBeTruthy();
    expect(response.body.postType).toBe('image');
    expect(response.body.imageUrls).toBeInstanceOf(Array);
    expect(response.body.imageUrls[0]).toMatch(/^https?:\/\//);
    expect(response.body.mentionUsernames).toContain(secondary.username.toLowerCase());
  });

  it('authenticates realtime namespaces and rejects anonymous sockets', async () => {
    const address = app.getHttpServer().address();
    if (!address || typeof address === 'string') throw new Error('Test server address is unavailable');
    const baseUrl = `http://127.0.0.1:${address.port}`;

    const connectAuthenticated = (namespace: string) => new Promise<Socket>((resolve, reject) => {
      const socket = io(`${baseUrl}/${namespace}`, {
        auth: { token: primary.accessToken }, transports: ['websocket'], forceNew: true,
      });
      const timer = setTimeout(() => { socket.disconnect(); reject(new Error(`${namespace} connection timeout`)); }, 5000);
      socket.once('connect', () => { clearTimeout(timer); resolve(socket); });
      socket.once('connect_error', (error) => { clearTimeout(timer); socket.disconnect(); reject(error); });
    });

    const sockets = await Promise.all([
      connectAuthenticated('notifications'),
      connectAuthenticated('chat'),
    ]);
    expect(sockets.every((socket) => socket.connected)).toBe(true);
    sockets.forEach((socket) => socket.disconnect());

    const anonymousRejected = await new Promise<boolean>((resolve) => {
      const socket = io(`${baseUrl}/notifications`, { transports: ['websocket'], forceNew: true });
      const timer = setTimeout(() => { socket.disconnect(); resolve(false); }, 5000);
      socket.once('disconnect', () => { clearTimeout(timer); resolve(true); });
      socket.once('connect_error', () => { clearTimeout(timer); socket.disconnect(); resolve(true); });
    });
    expect(anonymousRejected).toBe(true);
  });

  it('keeps likes idempotent under concurrent duplicate requests', async () => {
    const responses = await Promise.all(
      Array.from({ length: 20 }, () =>
        request(app.getHttpServer())
          .post('/api/v1/likes')
          .set('Authorization', `Bearer ${secondary.accessToken}`)
          .send({ targetType: 'post', targetId: postId }),
      ),
    );
    expect(responses.every((response) => response.status === 201)).toBe(true);

    const postResponse = await request(app.getHttpServer())
      .get(`/api/v1/posts/${postId}`)
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .expect(200);
    expect(postResponse.body.likesCount).toBe(1);

    await request(app.getHttpServer())
      .delete(`/api/v1/likes/post/${postId}`)
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .expect(200);
  });

  it('returns unified pagination in feed', async () => {
    const response = await request(app.getHttpServer())
      .get('/api/v1/feed/me?includeSuggestions=false&limit=10')
      .set('Authorization', `Bearer ${primary.accessToken}`)
      .expect(200);

    expect(response.body.items).toBeInstanceOf(Array);
    expect(response.body.pagination).toEqual(
      expect.objectContaining({
        mode: 'cursor',
        limit: 10,
      }),
    );
  });

  it('tracks feed quality signals and supports not-interested recovery', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/engagement/events/batch')
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .send({ events: [
        { postId, type: 'impression', sessionId: `smoke-${ts}` },
        { postId, type: 'watch', watchTimeMs: 4200, progressPercent: 85, sessionId: `smoke-${ts}` },
        { postId, type: 'complete', progressPercent: 100, sessionId: `smoke-${ts}` },
      ] })
      .expect(202);

    await request(app.getHttpServer())
      .post(`/api/v1/engagement/posts/${postId}/not-interested`)
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .expect(201);

    const feed = await request(app.getHttpServer())
      .get('/api/v1/feed/me?includeSuggestions=false&limit=20')
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .expect(200);
    expect(feed.body.items.some((item: any) => (item._id || item.id) === postId)).toBe(false);

    await request(app.getHttpServer())
      .delete(`/api/v1/engagement/posts/${postId}/not-interested`)
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .expect(200);

    const summary = await request(app.getHttpServer())
      .get('/api/v1/engagement/me/summary')
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .expect(200);
    expect(summary.body).toEqual(expect.objectContaining({ periodDays: 30, byType: expect.any(Object) }));
  });

  it('creates mention notification for mentioned post user', async () => {
    const response = await request(app.getHttpServer())
      .get('/api/v1/notifications')
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .expect(200);

    const mention = (response.body.items ?? []).find(
      (item: any) => item.type === 'mention' && (item.referenceId === postId || item.deepLink === `/posts/${postId}`),
    );

    expect(response.body.pagination).toBeTruthy();
    expect(mention).toBeTruthy();
  });

  it('creates comment mention notification for a third user', async () => {
    await request(app.getHttpServer())
      .post('/api/v1/comments')
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .send({
        postId,
        content: `Great take @${tertiary.username}`,
        mentionUsernames: [tertiary.username],
      })
      .expect(201);

    const commentsResponse = await request(app.getHttpServer())
      .get(`/api/v1/comments/post/${postId}?page=1&limit=10`)
      .set('Authorization', `Bearer ${primary.accessToken}`)
      .expect(200);

    expect(commentsResponse.body.pagination).toEqual(
      expect.objectContaining({
        mode: 'offset',
        limit: 10,
      }),
    );

    const notificationsResponse = await request(app.getHttpServer())
      .get('/api/v1/notifications')
      .set('Authorization', `Bearer ${tertiary.accessToken}`)
      .expect(200);

    const mention = (notificationsResponse.body.items ?? []).find(
      (item: any) => item.type === 'mention' && item.resourceType === 'comment',
    );

    expect(mention).toBeTruthy();
  });

  it('updates own comment content and mention usernames', async () => {
    const createResponse = await request(app.getHttpServer())
      .post('/api/v1/comments')
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .send({
        postId,
        content: 'Needs edit',
      })
      .expect(201);

    editableCommentId = createResponse.body._id || createResponse.body.id;

    const updatedContent = `Edited with @${primary.username}`;
    const updateResponse = await request(app.getHttpServer())
      .patch(`/api/v1/comments/${editableCommentId}`)
      .set('Authorization', `Bearer ${secondary.accessToken}`)
      .send({
        content: updatedContent,
        mentionUsernames: [primary.username],
      })
      .expect(200);

    expect(updateResponse.body.content).toBe(updatedContent);
    expect(updateResponse.body.mentionUsernames).toContain(primary.username.toLowerCase());

    const commentsResponse = await request(app.getHttpServer())
      .get(`/api/v1/comments/post/${postId}?page=1&limit=20`)
      .set('Authorization', `Bearer ${primary.accessToken}`)
      .expect(200);

    const updatedComment = (commentsResponse.body.items ?? []).find(
      (item: any) => (item._id || item.id) === editableCommentId,
    );

    expect(updatedComment?.content).toBe(updatedContent);

    const notificationsResponse = await request(app.getHttpServer())
      .get('/api/v1/notifications?resourceType=comment')
      .set('Authorization', `Bearer ${primary.accessToken}`)
      .expect(200);

    const mention = (notificationsResponse.body.items ?? []).find(
      (item: any) =>
        item.type === 'mention' &&
        item.resourceType === 'comment' &&
        item.previewText === updatedContent,
    );

    expect(mention).toBeTruthy();
  });

  it('supports superadmin sessions refresh rotation and notifications access', async () => {
    const loginOne = await request(app.getHttpServer())
      .post('/api/v1/auth/superadmin/login')
      .send({
        email: process.env.SUPERADMIN_EMAIL,
        password: process.env.SUPERADMIN_PASSWORD,
      })
      .expect(200);

    superAdmin.accessToken = loginOne.body.accessToken;
    superAdmin.refreshToken = loginOne.body.refreshToken;

    const sessionsAfterFirstLogin = await request(app.getHttpServer())
      .get('/api/v1/auth/superadmin/sessions')
      .set('Authorization', `Bearer ${superAdmin.accessToken}`)
      .expect(200);

    const countAfterFirstLogin = (sessionsAfterFirstLogin.body.items ?? []).length;

    const loginTwo = await request(app.getHttpServer())
      .post('/api/v1/auth/superadmin/login')
      .send({
        email: process.env.SUPERADMIN_EMAIL,
        password: process.env.SUPERADMIN_PASSWORD,
      })
      .expect(200);

    superAdmin.secondAccessToken = loginTwo.body.accessToken;
    superAdmin.secondRefreshToken = loginTwo.body.refreshToken;

    const initialSessions = await request(app.getHttpServer())
      .get('/api/v1/auth/superadmin/sessions')
      .set('Authorization', `Bearer ${superAdmin.accessToken}`)
      .expect(200);

    const initialItems = initialSessions.body.items ?? [];
    expect(initialItems).toHaveLength(countAfterFirstLogin + 1);

    const recentSessionIds = initialItems
      .slice(0, 2)
      .map((item: { id?: string }) => item.id)
      .filter((id: string | undefined): id is string => Boolean(id));

    const refreshResponse = await request(app.getHttpServer())
      .post('/api/v1/auth/superadmin/refresh')
      .send({ refreshToken: superAdmin.refreshToken })
      .expect(200);

    superAdmin.accessToken = refreshResponse.body.accessToken;
    superAdmin.refreshToken = refreshResponse.body.refreshToken;

    const refreshedSessions = await request(app.getHttpServer())
      .get('/api/v1/auth/superadmin/sessions')
      .set('Authorization', `Bearer ${superAdmin.accessToken}`)
      .expect(200);

    const refreshedItems = refreshedSessions.body.items ?? [];
    expect(refreshedItems).toHaveLength(initialItems.length);

    const notificationsResponse = await request(app.getHttpServer())
      .get('/api/v1/notifications/superadmin?limit=10')
      .set('Authorization', `Bearer ${superAdmin.accessToken}`)
      .expect(200);

    expect(notificationsResponse.body.items).toBeInstanceOf(Array);

    const secondarySession = refreshedItems.find(
      (item: { id?: string }) => item.id && recentSessionIds.includes(item.id),
    );

    expect(secondarySession?.id).toBeTruthy();

    await request(app.getHttpServer())
      .post(`/api/v1/auth/superadmin/sessions/${secondarySession.id}/revoke`)
      .set('Authorization', `Bearer ${superAdmin.accessToken}`)
      .expect(201);

    const sessionsAfterRevoke = await request(app.getHttpServer())
      .get('/api/v1/auth/superadmin/sessions')
      .set('Authorization', `Bearer ${superAdmin.accessToken}`)
      .expect(200);

    expect(sessionsAfterRevoke.body.items ?? []).toHaveLength(initialItems.length - 1);
  });
});
