const request = require('supertest');
const express = require('express');

// Mock s3Service
const mockGetPresignedUploadUrl = jest.fn();
const mockVerifyUpload = jest.fn();
const mockGetPresignedDownloadUrl = jest.fn();
const mockIsEnabled = jest.fn();

jest.mock('../../../services/s3Service', () => ({
  isEnabled: mockIsEnabled,
  getPresignedUploadUrl: mockGetPresignedUploadUrl,
  verifyUpload: mockVerifyUpload,
  getPresignedDownloadUrl: mockGetPresignedDownloadUrl
}));

// Mock S3OwnershipService
const mockCanAccessFile = jest.fn();

jest.mock('../../../services/s3OwnershipService', () => ({
  canAccessFile: mockCanAccessFile
}));

// Mock auth middleware
jest.mock('../../../middleware/auth', () => ({
  authenticateToken: (req, res, next) => {
    if (req.headers.authorization === 'Bearer valid-token') {
      req.user = { id: 'user-123' };
      next();
    } else {
      res.status(401).json({ error: 'No token provided' });
    }
  }
}));

// Mock rate limiter
jest.mock('../../../middleware/rateLimiter', () => ({
  uploadPresignLimiter: (req, res, next) => next()
}));

// Mock logger
jest.mock('../../../utils/logger', () => ({
  info: jest.fn(),
  warn: jest.fn(),
  error: jest.fn()
}));

const uploadRoutes = require('../../../routes/upload');

// Set up Express app for testing
const app = express();
app.use(express.json());
app.use('/upload', uploadRoutes);

// Error handler
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message });
});

describe('Upload Routes', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockIsEnabled.mockReturnValue(true);
  });

  describe('POST /upload/presign', () => {
    const validRequest = {
      uploadType: 'item',
      contentType: 'image/jpeg',
      fileName: 'photo.jpg',
      fileSize: 1024 * 1024
    };

    const mockPresignResponse = {
      uploadUrl: 'https://presigned-url.s3.amazonaws.com',
      key: 'items/uuid.jpg',
      publicUrl: 'https://bucket.s3.amazonaws.com/items/uuid.jpg',
      expiresAt: new Date()
    };

    it('should return presigned URL for valid request', async () => {
      mockGetPresignedUploadUrl.mockResolvedValue(mockPresignResponse);

      const response = await request(app)
        .post('/upload/presign')
        .set('Authorization', 'Bearer valid-token')
        .send(validRequest);

      expect(response.status).toBe(200);
      expect(response.body).toEqual(expect.objectContaining({
        uploadUrl: mockPresignResponse.uploadUrl,
        key: mockPresignResponse.key,
        publicUrl: mockPresignResponse.publicUrl
      }));

      expect(mockGetPresignedUploadUrl).toHaveBeenCalledWith(
        'item',
        'image/jpeg',
        'photo.jpg',
        1024 * 1024
      );
    });

    it('should require authentication', async () => {
      const response = await request(app)
        .post('/upload/presign')
        .send(validRequest);

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

    it('should return 503 when S3 is disabled', async () => {
      mockIsEnabled.mockReturnValue(false);

      const response = await request(app)
        .post('/upload/presign')
        .set('Authorization', 'Bearer valid-token')
        .send(validRequest);

      expect(response.status).toBe(503);
      expect(response.body.error).toBe('File upload service is not available');
    });

    it('should return 400 when uploadType is missing', async () => {
      const response = await request(app)
        .post('/upload/presign')
        .set('Authorization', 'Bearer valid-token')
        .send({ ...validRequest, uploadType: undefined });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Missing required fields');
    });

    it('should return 400 when contentType is missing', async () => {
      const response = await request(app)
        .post('/upload/presign')
        .set('Authorization', 'Bearer valid-token')
        .send({ ...validRequest, contentType: undefined });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Missing required fields');
    });

    it('should return 400 when fileName is missing', async () => {
      const response = await request(app)
        .post('/upload/presign')
        .set('Authorization', 'Bearer valid-token')
        .send({ ...validRequest, fileName: undefined });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Missing required fields');
    });

    it('should return 400 when fileSize is missing', async () => {
      const response = await request(app)
        .post('/upload/presign')
        .set('Authorization', 'Bearer valid-token')
        .send({ ...validRequest, fileSize: undefined });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Missing required fields');
    });

    it('should return 400 for invalid upload type', async () => {
      mockGetPresignedUploadUrl.mockRejectedValue(new Error('Invalid upload type: invalid'));

      const response = await request(app)
        .post('/upload/presign')
        .set('Authorization', 'Bearer valid-token')
        .send({ ...validRequest, uploadType: 'invalid' });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('Invalid');
    });

    it('should return 400 for invalid content type', async () => {
      mockGetPresignedUploadUrl.mockRejectedValue(new Error('Invalid content type: application/pdf'));

      const response = await request(app)
        .post('/upload/presign')
        .set('Authorization', 'Bearer valid-token')
        .send({ ...validRequest, contentType: 'application/pdf' });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('Invalid');
    });

    it('should return 400 for file too large', async () => {
      mockGetPresignedUploadUrl.mockRejectedValue(new Error('Invalid: File too large'));

      const response = await request(app)
        .post('/upload/presign')
        .set('Authorization', 'Bearer valid-token')
        .send({ ...validRequest, fileSize: 100 * 1024 * 1024 });

      expect(response.status).toBe(400);
    });
  });

  describe('POST /upload/presign-batch', () => {
    const validBatchRequest = {
      uploadType: 'item',
      files: [
        { contentType: 'image/jpeg', fileName: 'photo1.jpg', fileSize: 1024 },
        { contentType: 'image/png', fileName: 'photo2.png', fileSize: 2048 }
      ]
    };

    const mockPresignResponse = {
      uploadUrl: 'https://presigned-url.s3.amazonaws.com',
      key: 'items/uuid.jpg',
      publicUrl: 'https://bucket.s3.amazonaws.com/items/uuid.jpg',
      expiresAt: new Date()
    };

    it('should return presigned URLs for multiple files', async () => {
      mockGetPresignedUploadUrl.mockResolvedValue(mockPresignResponse);

      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send(validBatchRequest);

      expect(response.status).toBe(200);
      expect(response.body.uploads).toHaveLength(2);
      expect(mockGetPresignedUploadUrl).toHaveBeenCalledTimes(2);
    });

    it('should require authentication', async () => {
      const response = await request(app)
        .post('/upload/presign-batch')
        .send(validBatchRequest);

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

    it('should return 503 when S3 is disabled', async () => {
      mockIsEnabled.mockReturnValue(false);

      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send(validBatchRequest);

      expect(response.status).toBe(503);
    });

    it('should return 400 when uploadType is missing', async () => {
      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send({ files: validBatchRequest.files });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Missing required fields');
    });

    it('should return 400 when files is not an array', async () => {
      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send({ uploadType: 'item', files: 'not-an-array' });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Missing required fields');
    });

    it('should return 400 when files array is empty', async () => {
      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send({ uploadType: 'item', files: [] });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('No files specified');
    });

    it('should return 400 when exceeding max batch size (20)', async () => {
      const tooManyFiles = Array(21).fill({
        contentType: 'image/jpeg',
        fileName: 'photo.jpg',
        fileSize: 1024
      });

      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send({ uploadType: 'item', files: tooManyFiles });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('Maximum');
    });

    it('should return 400 when file is missing contentType', async () => {
      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send({
          uploadType: 'item',
          files: [{ fileName: 'photo.jpg', fileSize: 1024 }]
        });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('contentType');
    });

    it('should return 400 when file is missing fileName', async () => {
      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send({
          uploadType: 'item',
          files: [{ contentType: 'image/jpeg', fileSize: 1024 }]
        });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('fileName');
    });

    it('should return 400 when file is missing fileSize', async () => {
      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send({
          uploadType: 'item',
          files: [{ contentType: 'image/jpeg', fileName: 'photo.jpg' }]
        });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain('fileSize');
    });

    it('should accept exactly 20 files', async () => {
      mockGetPresignedUploadUrl.mockResolvedValue(mockPresignResponse);

      const maxFiles = Array(20).fill({
        contentType: 'image/jpeg',
        fileName: 'photo.jpg',
        fileSize: 1024
      });

      const response = await request(app)
        .post('/upload/presign-batch')
        .set('Authorization', 'Bearer valid-token')
        .send({ uploadType: 'item', files: maxFiles });

      expect(response.status).toBe(200);
      expect(response.body.uploads).toHaveLength(20);
    });
  });

  describe('POST /upload/confirm', () => {
    const validConfirmRequest = {
      keys: ['items/uuid1.jpg', 'items/uuid2.jpg']
    };

    it('should confirm uploaded files', async () => {
      mockVerifyUpload.mockResolvedValue(true);

      const response = await request(app)
        .post('/upload/confirm')
        .set('Authorization', 'Bearer valid-token')
        .send(validConfirmRequest);

      expect(response.status).toBe(200);
      expect(response.body.confirmed).toEqual(validConfirmRequest.keys);
      expect(response.body.total).toBe(2);
    });

    it('should return only confirmed keys', async () => {
      mockVerifyUpload
        .mockResolvedValueOnce(true)
        .mockResolvedValueOnce(false);

      const response = await request(app)
        .post('/upload/confirm')
        .set('Authorization', 'Bearer valid-token')
        .send(validConfirmRequest);

      expect(response.status).toBe(200);
      expect(response.body.confirmed).toHaveLength(1);
      expect(response.body.confirmed[0]).toBe('items/uuid1.jpg');
      expect(response.body.total).toBe(2);
    });

    it('should require authentication', async () => {
      const response = await request(app)
        .post('/upload/confirm')
        .send(validConfirmRequest);

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

    it('should return 503 when S3 is disabled', async () => {
      mockIsEnabled.mockReturnValue(false);

      const response = await request(app)
        .post('/upload/confirm')
        .set('Authorization', 'Bearer valid-token')
        .send(validConfirmRequest);

      expect(response.status).toBe(503);
    });

    it('should return 400 when keys is missing', async () => {
      const response = await request(app)
        .post('/upload/confirm')
        .set('Authorization', 'Bearer valid-token')
        .send({});

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Missing keys array');
    });

    it('should return 400 when keys is not an array', async () => {
      const response = await request(app)
        .post('/upload/confirm')
        .set('Authorization', 'Bearer valid-token')
        .send({ keys: 'not-an-array' });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Missing keys array');
    });

    it('should return 400 when keys array is empty', async () => {
      const response = await request(app)
        .post('/upload/confirm')
        .set('Authorization', 'Bearer valid-token')
        .send({ keys: [] });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('No keys specified');
    });

    it('should handle all files not found', async () => {
      mockVerifyUpload.mockResolvedValue(false);

      const response = await request(app)
        .post('/upload/confirm')
        .set('Authorization', 'Bearer valid-token')
        .send(validConfirmRequest);

      expect(response.status).toBe(200);
      expect(response.body.confirmed).toHaveLength(0);
      expect(response.body.total).toBe(2);
    });
  });

  describe('GET /upload/signed-url/:key(*)', () => {
    const mockSignedUrl = 'https://bucket.s3.amazonaws.com/messages/uuid.jpg?signature=abc';

    beforeEach(() => {
      mockGetPresignedDownloadUrl.mockResolvedValue(mockSignedUrl);
      mockCanAccessFile.mockResolvedValue({ authorized: true });
    });

    it('should return signed URL for authorized private content (messages)', async () => {
      const response = await request(app)
        .get('/upload/signed-url/messages/550e8400-e29b-41d4-a716-446655440000.jpg')
        .set('Authorization', 'Bearer valid-token');

      expect(response.status).toBe(200);
      expect(response.body.url).toBe(mockSignedUrl);
      expect(response.body.expiresIn).toBe(3600);

      expect(mockCanAccessFile).toHaveBeenCalledWith(
        'messages/550e8400-e29b-41d4-a716-446655440000.jpg',
        'user-123'
      );
      expect(mockGetPresignedDownloadUrl).toHaveBeenCalledWith(
        'messages/550e8400-e29b-41d4-a716-446655440000.jpg'
      );
    });

    it('should return signed URL for authorized condition-check content', async () => {
      const response = await request(app)
        .get('/upload/signed-url/condition-checks/550e8400-e29b-41d4-a716-446655440000.jpg')
        .set('Authorization', 'Bearer valid-token');

      expect(response.status).toBe(200);
      expect(response.body.url).toBe(mockSignedUrl);

      expect(mockCanAccessFile).toHaveBeenCalledWith(
        'condition-checks/550e8400-e29b-41d4-a716-446655440000.jpg',
        'user-123'
      );
    });

    it('should require authentication', async () => {
      const response = await request(app)
        .get('/upload/signed-url/messages/uuid.jpg');

      expect(response.status).toBe(401);
      expect(mockGetPresignedDownloadUrl).not.toHaveBeenCalled();
    });

    it('should return 503 when S3 is disabled', async () => {
      mockIsEnabled.mockReturnValue(false);

      const response = await request(app)
        .get('/upload/signed-url/messages/uuid.jpg')
        .set('Authorization', 'Bearer valid-token');

      expect(response.status).toBe(503);
    });

    it('should return 400 for public folder paths (items)', async () => {
      const response = await request(app)
        .get('/upload/signed-url/items/uuid.jpg')
        .set('Authorization', 'Bearer valid-token');

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Signed URLs only for private content');
      expect(mockGetPresignedDownloadUrl).not.toHaveBeenCalled();
    });

    it('should return 400 for public folder paths (profiles)', async () => {
      const response = await request(app)
        .get('/upload/signed-url/profiles/uuid.jpg')
        .set('Authorization', 'Bearer valid-token');

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Signed URLs only for private content');
    });

    it('should return 400 for public folder paths (forum)', async () => {
      const response = await request(app)
        .get('/upload/signed-url/forum/uuid.jpg')
        .set('Authorization', 'Bearer valid-token');

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Signed URLs only for private content');
    });

    it('should return 403 when user is not authorized to access file', async () => {
      mockCanAccessFile.mockResolvedValue({
        authorized: false,
        reason: 'Not a participant in this message'
      });

      const response = await request(app)
        .get('/upload/signed-url/messages/uuid.jpg')
        .set('Authorization', 'Bearer valid-token');

      expect(response.status).toBe(403);
      expect(response.body.error).toBe('Access denied');
      expect(mockGetPresignedDownloadUrl).not.toHaveBeenCalled();
    });

    it('should handle URL-encoded keys', async () => {
      const response = await request(app)
        .get('/upload/signed-url/messages%2Fuuid.jpg')
        .set('Authorization', 'Bearer valid-token');

      // The key should be decoded
      expect(mockCanAccessFile).toHaveBeenCalledWith(
        expect.stringContaining('messages'),
        'user-123'
      );
    });

    it('should handle S3 service errors gracefully', async () => {
      mockGetPresignedDownloadUrl.mockRejectedValue(new Error('S3 error'));

      const response = await request(app)
        .get('/upload/signed-url/messages/uuid.jpg')
        .set('Authorization', 'Bearer valid-token');

      expect(response.status).toBe(500);
    });

    it('should handle ownership service errors gracefully', async () => {
      mockCanAccessFile.mockRejectedValue(new Error('Database error'));

      const response = await request(app)
        .get('/upload/signed-url/messages/uuid.jpg')
        .set('Authorization', 'Bearer valid-token');

      expect(response.status).toBe(500);
    });
  });
});
