import { test, expect, registerUser } from '../fixtures';
import { createTestListing, createListing } from '../fixtures/listings.fixture';

test.describe('Listings API', () => {
  let accessToken: string;

  test.beforeAll(async ({ request }) => {
    const { accessToken: token } = await registerUser(request);
    accessToken = token;
  });

  test.describe('POST /listings — Create listing', () => {
    test('creates a listing with valid data', async ({ request }) => {
      const data = createTestListing();
      const res = await request.post('listings', {
        data,
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      expect(res.status()).toBe(201);
      const body = await res.json();
      expect(body).toHaveProperty('listingId');
      expect(body).toHaveProperty('status');
    });

    test('rejects listing with missing required fields', async ({ request }) => {
      const res = await request.post('listings', {
        data: { title: 'Incomplete' },
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      expect(res.ok()).toBeFalsy();
      expect(res.status()).toBe(400);
    });

    test('rejects listing with invalid property type', async ({ request }) => {
      const data = createTestListing({ propertyType: 'INVALID_TYPE' });
      const res = await request.post('listings', {
        data,
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      expect(res.ok()).toBeFalsy();
      expect(res.status()).toBe(400);
    });

    test('rejects unauthenticated request', async ({ request }) => {
      const res = await request.post('listings', {
        data: createTestListing(),
      });

      expect(res.ok()).toBeFalsy();
      expect(res.status()).toBe(401);
    });

    test('creates a RENT listing', async ({ request }) => {
      const data = createTestListing({
        transactionType: 'RENT',
        rentPriceMonthly: '15000000',
      });
      const res = await request.post('listings', {
        data,
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      expect(res.status()).toBe(201);
      const body = await res.json();
      expect(body).toHaveProperty('listingId');
    });
  });

  test.describe('GET /listings — Search listings', () => {
    test('returns paginated listing results', async ({ request }) => {
      const res = await request.get('listings');

      expect(res.ok()).toBeTruthy();
      const body = await res.json();
      expect(body).toHaveProperty('data');
      expect(Array.isArray(body.data)).toBeTruthy();
      expect(body).toHaveProperty('total');
    });

    test('filters by property type', async ({ request }) => {
      const res = await request.get('listings', {
        params: { propertyType: 'APARTMENT' },
      });

      expect(res.ok()).toBeTruthy();
      const body = await res.json();
      for (const listing of body.data) {
        expect(listing.property.propertyType).toBe('APARTMENT');
      }
    });

    test('filters by transaction type', async ({ request }) => {
      const res = await request.get('listings', {
        params: { transactionType: 'SALE' },
      });

      expect(res.ok()).toBeTruthy();
      const body = await res.json();
      for (const listing of body.data) {
        expect(listing.transactionType).toBe('SALE');
      }
    });

    test('filters by city', async ({ request }) => {
      const res = await request.get('listings', {
        params: { city: 'Hồ Chí Minh' },
      });

      expect(res.ok()).toBeTruthy();
    });

    test('paginates correctly', async ({ request }) => {
      const res = await request.get('listings', {
        params: { page: 1, limit: 2 },
      });

      expect(res.ok()).toBeTruthy();
      const body = await res.json();
      expect(body.data.length).toBeLessThanOrEqual(2);
    });
  });

  test.describe('GET /listings/:id — Get listing detail', () => {
    test('returns listing by id', async ({ request }) => {
      // First create a listing
      const { listing } = await createListing(request, accessToken);

      const res = await request.get(`listings/${listing.listingId}`);

      expect(res.status()).toBe(200);
      const body = await res.json();
      expect(body.id).toBe(listing.listingId);
      expect(body).toHaveProperty('property');
      expect(body.property).toHaveProperty('title');
      expect(body.property).toHaveProperty('address');
    });

    test('returns 404 for non-existent listing', async ({ request }) => {
      const res = await request.get('listings/non-existent-id-12345');

      expect(res.ok()).toBeFalsy();
      expect(res.status()).toBe(404);
    });
  });

  test.describe('PATCH /listings/:id — Update listing content', () => {
    test('updates title and description', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.patch(`listings/${listing.listingId}`, {
        data: {
          title: 'Tiêu đề cập nhật qua E2E test',
          description: 'Mô tả chi tiết cập nhật qua E2E test cho căn hộ',
        },
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      expect(res.status()).toBe(200);
      const body = await res.json();
      expect(body.listingId).toBe(listing.listingId);
      expect(body.updatedFields).toContain('title');
      expect(body.updatedFields).toContain('description');
    });

    test('updates price', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.patch(`listings/${listing.listingId}`, {
        data: { priceVND: '6000000000' },
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      expect(res.status()).toBe(200);
      const body = await res.json();
      expect(body.updatedFields).toContain('priceVND');
    });

    test('updates amenities', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.patch(`listings/${listing.listingId}`, {
        data: { amenities: ['Hồ bơi', 'Gym', 'Sân tennis'] },
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      expect(res.status()).toBe(200);
      const body = await res.json();
      expect(body.updatedFields).toContain('amenities');
    });

    test('rejects update with no fields', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.patch(`listings/${listing.listingId}`, {
        data: {},
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      expect(res.ok()).toBeFalsy();
      expect(res.status()).toBe(400);
    });

    test('rejects unauthenticated update', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.patch(`listings/${listing.listingId}`, {
        data: { title: 'Unauthorized update attempt' },
      });

      expect(res.status()).toBe(401);
    });

    test('rejects update from non-owner', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      // Register a different user
      const { accessToken: otherToken } = await registerUser(request);

      const res = await request.patch(`listings/${listing.listingId}`, {
        data: { title: 'Hacker update attempt here' },
        headers: { Authorization: `Bearer ${otherToken}` },
      });

      expect(res.ok()).toBeFalsy();
      expect(res.status()).toBe(403);
    });

    test('rejects forbidden fields (propertyType, address) via whitelist', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.patch(`listings/${listing.listingId}`, {
        data: {
          title: 'Valid title for update',
          propertyType: 'VILLA',
          address: '456 New Street',
        },
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      // With whitelist validation, forbidden fields should cause 400
      // OR be silently stripped — either way propertyType should NOT change
      if (res.ok()) {
        const body = await res.json();
        expect(body.updatedFields).not.toContain('propertyType');
        expect(body.updatedFields).not.toContain('address');
      } else {
        expect(res.status()).toBe(400);
      }
    });
  });

  test.describe('PATCH /listings/:id/status — Update listing status', () => {
    test('updates listing status', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.patch(`listings/${listing.listingId}/status`, {
        data: { status: 'ACTIVE' },
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      // DRAFT → ACTIVE may be rejected by business rules (e.g. moderation required)
      expect([200, 400, 403]).toContain(res.status());
    });

    test('rejects invalid status value', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.patch(`listings/${listing.listingId}/status`, {
        data: { status: 'INVALID_STATUS' },
        headers: { Authorization: `Bearer ${accessToken}` },
      });

      expect(res.ok()).toBeFalsy();
      expect(res.status()).toBe(400);
    });

    test('rejects unauthenticated status update', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.patch(`listings/${listing.listingId}/status`, {
        data: { status: 'ACTIVE' },
      });

      expect(res.status()).toBe(401);
    });
  });

  test.describe('GET /listings/:id/price-history — Price history timeline', () => {
    test('returns empty array when listing has no price changes', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const res = await request.get(`listings/${listing.listingId}/price-history`);

      expect(res.status()).toBe(200);
      const body = await res.json();
      expect(Array.isArray(body)).toBe(true);
      expect(body).toHaveLength(0);
    });

    test('returns a price history entry after price update', async ({ request }) => {
      const { listing } = await createListing(request, accessToken);

      const updateRes = await request.patch(`listings/${listing.listingId}`, {
        data: { priceVND: '6000000000' },
        headers: { Authorization: `Bearer ${accessToken}` },
      });
      expect(updateRes.status()).toBe(200);

      // Event bus is async — poll briefly for the snapshot to land.
      const deadline = Date.now() + 5000;
      let body: Array<{ oldPrice: string; newPrice: string; source: string; changedAt: string }> = [];
      while (Date.now() < deadline) {
        const res = await request.get(`listings/${listing.listingId}/price-history`);
        expect(res.status()).toBe(200);
        body = await res.json();
        if (body.length > 0) break;
        await new Promise((r) => setTimeout(r, 100));
      }

      expect(body.length).toBeGreaterThanOrEqual(1);
      const entry = body[0]!;
      expect(entry).toHaveProperty('oldPrice');
      expect(entry).toHaveProperty('newPrice');
      expect(entry).toHaveProperty('source');
      expect(entry).toHaveProperty('changedAt');
      expect(entry.source).toBe('manual_update');
    });
  });
});
