import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import request from 'supertest';
import { JwtModule, JwtService } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MessagesController } from '../../src/modules/messages/messages.controller';
import { MessagesService } from '../../src/modules/messages/messages.service';
import { SearchService } from '../../src/modules/search/search.service';
import { PrismaService } from '@chatapp/database';
import { generateTestToken } from '../setup';

const USER_ID = 'user-msg-001';
const WS_ID = 'ws-msg-001';
const CHANNEL_ID = 'channel-msg-001';

function createMockPrisma() {
  const messages: Record<string, any> = {};
  const reactions: any[] = [];
  const savedMessages: any[] = [];

  return {
    message: {
      findFirst: jest.fn(({ where }: any) => {
        return Object.values(messages).find((m: any) =>
          m.id === where.id &&
          (!where.workspaceId || m.workspaceId === where.workspaceId) &&
          (!where.isDeleted === false || !m.isDeleted)
        ) || null;
      }),
      findUnique: jest.fn(({ where }: any) => messages[where.id] || null),
      findMany: jest.fn(({ where }: any) => {
        let results = Object.values(messages);
        if (where?.channelId) {
          results = results.filter((m: any) => m.channelId === where.channelId);
        }
        if (where?.threadRootId) {
          results = results.filter((m: any) => m.threadRootId === where.threadRootId);
        }
        if (where?.isDeleted === false) {
          results = results.filter((m: any) => !m.isDeleted);
        }
        return results;
      }),
      create: jest.fn(({ data, include }: any) => {
        const message = {
          id: 'msg-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7),
          ...data,
          isEdited: false,
          isDeleted: false,
          isPinned: false,
          replyCount: 0,
          createdAt: new Date(),
          updatedAt: new Date(),
          ts: new Date(),
          user: { id: data.userId, displayName: 'Test User' },
          channel: { id: data.channelId, name: 'test-channel' },
          reactions: [],
        };
        messages[message.id] = message;
        return message;
      }),
      update: jest.fn(({ where, data }: any) => {
        if (!messages[where.id]) throw new Error('Message not found');
        messages[where.id] = { ...messages[where.id], ...data };
        return messages[where.id];
      }),
    },
    channel: {
      update: jest.fn(),
      findFirst: jest.fn().mockResolvedValue({ id: CHANNEL_ID, workspaceId: WS_ID, name: 'test-channel' }),
    },
    channelMember: {
      findFirst: jest.fn().mockResolvedValue({ id: 'cm-1', userId: USER_ID, channelId: CHANNEL_ID, workspaceId: WS_ID, role: 'owner' }),
    },
    messageReaction: {
      create: jest.fn(({ data }: any) => {
        const reaction = {
          id: 'reaction-' + Date.now(),
          ...data,
          createdAt: new Date(),
        };
        reactions.push(reaction);
        return reaction;
      }),
      deleteMany: jest.fn(({ where }: any) => {
        const before = reactions.length;
        const filtered = reactions.filter(
          (r) =>
            !(r.messageId === where.messageId &&
              r.userId === where.userId &&
              r.emoji === where.emoji),
        );
        reactions.length = 0;
        reactions.push(...filtered);
        return { count: before - filtered.length };
      }),
    },
    savedMessage: {
      create: jest.fn(({ data }: any) => {
        savedMessages.push(data);
        return data;
      }),
      deleteMany: jest.fn(({ where }: any) => {
        const before = savedMessages.length;
        const filtered = savedMessages.filter(
          (s) => !(s.userId === where.userId && s.messageId === where.messageId),
        );
        savedMessages.length = 0;
        savedMessages.push(...filtered);
        return { count: before - filtered.length };
      }),
    },
    $connect: jest.fn(),
    $disconnect: jest.fn(),
  };
}

describe('Messages E2E', () => {
  let app: INestApplication;
  let mockPrisma: any;
  let createdMessageId: string;

  beforeAll(async () => {
    mockPrisma = createMockPrisma();

    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot({ isGlobal: true }),
        JwtModule.registerAsync({
          imports: [ConfigModule],
          inject: [ConfigService],
          useFactory: (config: ConfigService) => ({
            secret: config.get<string>('JWT_SECRET'),
            signOptions: { expiresIn: '15m' },
          }),
        }),
      ],
      controllers: [MessagesController],
      providers: [
        MessagesService,
        { provide: PrismaService, useValue: mockPrisma },
        { provide: SearchService, useValue: { indexMessage: jest.fn().mockResolvedValue(undefined) } },
      ],
    }).compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
        transformOptions: { enableImplicitConversion: true },
      }),
    );
    app.setGlobalPrefix('api/v1');
    await app.init();
  });

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

  function authHeaders(token?: string): Record<string, string> {
    const t = token || generateTestToken(USER_ID, 'msguser@example.com');
    return {
      Authorization: `Bearer ${t}`,
      'X-Workspace-Id': WS_ID,
    };
  }

  describe('POST /api/v1/messages/:channelId', () => {
    it('should send a message', async () => {
      const res = await request(app.getHttpServer())
        .post(`/api/v1/messages/${CHANNEL_ID}`)
        .set(authHeaders())
        .send({ text: 'Hello, world!' })
        .expect(201);

      expect(res.body).toHaveProperty('id');
      expect(res.body).toHaveProperty('text', 'Hello, world!');
      expect(res.body).toHaveProperty('channelId', CHANNEL_ID);
      expect(res.body).toHaveProperty('userId', USER_ID);
      createdMessageId = res.body.id;
    });

    it('should send a thread reply', async () => {
      const res = await request(app.getHttpServer())
        .post(`/api/v1/messages/${CHANNEL_ID}`)
        .set(authHeaders())
        .send({
          text: 'Thread reply',
          thread_root_id: createdMessageId,
        })
        .expect(201);

      expect(res.body).toHaveProperty('threadRootId', createdMessageId);
    });

    it('should require authentication', async () => {
      await request(app.getHttpServer())
        .post(`/api/v1/messages/${CHANNEL_ID}`)
        .send({ text: 'No auth' })
        .expect(401);
    });
  });

  describe('PATCH /api/v1/messages/:id', () => {
    it('should edit a message', async () => {
      const res = await request(app.getHttpServer())
        .patch(`/api/v1/messages/${createdMessageId}`)
        .set(authHeaders())
        .send({ text: 'Edited message text' })
        .expect(200);

      expect(res.body).toHaveProperty('text', 'Edited message text');
      expect(res.body).toHaveProperty('isEdited', true);
      expect(res.body).toHaveProperty('editedBy', USER_ID);
    });

    it('should require authentication', async () => {
      await request(app.getHttpServer())
        .patch(`/api/v1/messages/${createdMessageId}`)
        .send({ text: 'No auth edit' })
        .expect(401);
    });
  });

  describe('POST /api/v1/messages/:id/reactions', () => {
    it('should add a reaction', async () => {
      const res = await request(app.getHttpServer())
        .post(`/api/v1/messages/${createdMessageId}/reactions`)
        .set(authHeaders())
        .send({ emoji: '👍' })
        .expect(201);

      expect(res.body).toHaveProperty('emoji', '👍');
      expect(res.body).toHaveProperty('userId', USER_ID);
    });

    it('should require authentication', async () => {
      await request(app.getHttpServer())
        .post(`/api/v1/messages/${createdMessageId}/reactions`)
        .send({ emoji: '❤️' })
        .expect(401);
    });
  });

  describe('DELETE /api/v1/messages/:id/reactions/:emoji', () => {
    it('should remove a reaction', async () => {
      const res = await request(app.getHttpServer())
        .delete(`/api/v1/messages/${createdMessageId}/reactions/👍`)
        .set(authHeaders())
        .expect(200);

      expect(res.body).toHaveProperty('removed', true);
    });
  });

  describe('GET /api/v1/messages/:id/thread', () => {
    it('should get thread replies', async () => {
      const res = await request(app.getHttpServer())
        .get(`/api/v1/messages/${createdMessageId}/thread`)
        .set(authHeaders())
        .expect(200);

      expect(res.body).toHaveProperty('messages');
      expect(Array.isArray(res.body.messages)).toBe(true);
      expect(res.body.messages.length).toBeGreaterThan(0);
    });

    it('should require authentication', async () => {
      await request(app.getHttpServer())
        .get(`/api/v1/messages/${createdMessageId}/thread`)
        .expect(401);
    });
  });

  describe('POST /api/v1/messages/:id/save', () => {
    it('should save a message', async () => {
      const res = await request(app.getHttpServer())
        .post(`/api/v1/messages/${createdMessageId}/save`)
        .set(authHeaders())
        .expect(201);

      expect(res.body).toHaveProperty('saved', true);
    });

    it('should unsave a message', async () => {
      const res = await request(app.getHttpServer())
        .delete(`/api/v1/messages/${createdMessageId}/save`)
        .set(authHeaders())
        .expect(200);

      expect(res.body).toHaveProperty('saved', false);
    });
  });

  describe('DELETE /api/v1/messages/:id', () => {
    it('should delete a message (soft delete)', async () => {
      const res = await request(app.getHttpServer())
        .delete(`/api/v1/messages/${createdMessageId}`)
        .set(authHeaders())
        .expect(200);

      expect(res.body).toHaveProperty('deleted', true);
    });

    it('should require authentication', async () => {
      await request(app.getHttpServer())
        .delete(`/api/v1/messages/${createdMessageId}`)
        .expect(401);
    });
  });
});