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

// Mock CouchDB service before importing routes
jest.mock('../../services/couchdbService', () => ({
  initialize: jest.fn().mockResolvedValue(true),
  create: jest.fn(),
  getById: jest.fn(),
  find: jest.fn(),
  createDocument: jest.fn().mockImplementation((doc) => Promise.resolve({
    _id: `street_${Date.now()}`,
    _rev: '1-test',
    type: 'street',
    ...doc
  })),
  updateDocument: jest.fn().mockImplementation((id, doc) => Promise.resolve({
    _id: id,
    _rev: '2-test',
    ...doc
  })),
  deleteDocument: jest.fn(),
  findByType: jest.fn().mockResolvedValue([]),
  findUserById: jest.fn(),
  findUserByEmail: jest.fn(),
  update: jest.fn(),
  getDocument: jest.fn(),
}));

const streetRoutes = require('../../routes/streets');
const Street = require('../../models/Street');
const User = require('../../models/User');
const { createTestUser, createTestStreet } = require('../utils/testHelpers');
const couchdbService = require('../../services/couchdbService');

const app = express();
app.use(express.json());
app.use('/api/streets', streetRoutes);

describe('Street Routes', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    
    // Mock Street model methods
    Street.find = jest.fn().mockResolvedValue([]);
    Street.findById = jest.fn().mockResolvedValue(null);
    Street.findOne = jest.fn().mockResolvedValue(null);
    Street.countDocuments = jest.fn().mockResolvedValue(0);
    Street.create = jest.fn().mockImplementation((data) => Promise.resolve({
      _id: `street_${Date.now()}`,
      _rev: '1-test',
      type: 'street',
      ...data
    }));
    
    // Mock User model methods
    User.findById = jest.fn().mockResolvedValue({
      _id: 'user_123',
      adoptedStreets: [],
      stats: {
        streetsAdopted: 0,
        tasksCompleted: 0,
        postsCreated: 0,
        eventsParticipated: 0,
        badgesEarned: 0
      },
      save: jest.fn().mockResolvedValue(true),
      toJSON: () => ({ 
        _id: 'user_123', 
        adoptedStreets: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        }
      })
    });
    
    // Mock couchdbService methods
    couchdbService.find.mockResolvedValue([]);
    couchdbService.getDocument.mockResolvedValue(null);
    couchdbService.createDocument.mockImplementation((doc) => Promise.resolve({
      _id: `street_${Date.now()}`,
      _rev: '1-test',
      ...doc
    }));
    couchdbService.updateDocument.mockImplementation((id, doc) => Promise.resolve({
      _id: id,
      _rev: '2-test',
      ...doc
    }));
    couchdbService.updateUserPoints = jest.fn().mockResolvedValue({
      _id: 'user_123',
      points: 50
    });
  });
  describe('GET /api/streets', () => {
    it('should get all streets', async () => {
      const { user } = await createTestUser();
      const street1 = await createTestStreet(user.id, { name: 'Main Street' });
      const street2 = await createTestStreet(user.id, { name: 'Oak Avenue' });

      // Create street objects with populate and save methods
      const street1WithPopulate = {
        ...street1,
        populate: jest.fn().mockResolvedValue(street1),
        save: jest.fn().mockResolvedValue(street1)
      };
      const street2WithPopulate = {
        ...street2,
        populate: jest.fn().mockResolvedValue(street2),
        save: jest.fn().mockResolvedValue(street2)
      };

      // Mock Street.find and couchdbService.find to return test streets
      Street.find.mockResolvedValue([street1WithPopulate, street2WithPopulate]);
      couchdbService.find.mockResolvedValue([street1, street2]);
      Street.countDocuments.mockResolvedValue(2);

      const response = await request(app)
        .get('/api/streets')
        .expect(200);

      expect(Array.isArray(response.body.data)).toBe(true);
      expect(response.body.data.length).toBe(2);
      expect(response.body.data[0]).toHaveProperty('name');
      expect(response.body.data[0]).toHaveProperty('location');
    });

    it('should return empty array when no streets exist', async () => {
      // Mock Street.find and couchdbService.find to return empty array
      Street.find.mockResolvedValue([]);
      couchdbService.find.mockResolvedValue([]);
      Street.countDocuments.mockResolvedValue(0);

      const response = await request(app)
        .get('/api/streets')
        .expect(200);

      expect(Array.isArray(response.body.data)).toBe(true);
      expect(response.body.data.length).toBe(0);
    });
  });

  describe('GET /api/streets/:id', () => {
    it('should get a single street by id', async () => {
      const { user } = await createTestUser();
      const street = await createTestStreet(user.id, { name: 'Elm Street' });

      // Create street object with populate, save, and toJSON methods
      const streetWithPopulate = {
        ...street,
        populate: jest.fn().mockResolvedValue(street),
        save: jest.fn().mockResolvedValue(street),
        toJSON: () => street
      };

      // Mock Street.findById and couchdbService.getDocument to return test street
      Street.findById.mockResolvedValue(streetWithPopulate);
      couchdbService.getDocument.mockResolvedValue(street);

      const response = await request(app)
        .get(`/api/streets/${street.id}`)
        .expect(200);



      expect(response.body).toHaveProperty('_id', street._id);
      expect(response.body).toHaveProperty('name', 'Elm Street');
    });

    it('should return 404 for non-existent street', async () => {
      const fakeId = '507f1f77bcf86cd799439011';

      // Mock Street.findById to return null (not found)
      Street.findById.mockResolvedValue(null);

      const response = await request(app)
        .get(`/api/streets/${fakeId}`)
        .expect(404);

      expect(response.body).toHaveProperty('msg', 'Street not found');
    });

    it('should handle invalid street ID format', async () => {
      // Mock Street.findById to throw an error for invalid ID
      Street.findById.mockRejectedValue(new Error('Invalid ID format'));

      const response = await request(app)
        .get('/api/streets/invalid-id')
        .expect(500);

      expect(response.body).toBeDefined();
    });
  });

  describe('POST /api/streets', () => {
    it('should create a new street with authentication', async () => {
      const { token } = await createTestUser();

      const streetData = {
        name: 'Broadway',
        location: {
          type: 'Point',
          coordinates: [-73.989308, 40.756432]
        }
      };

      // Mock Street.create to return created street
      const createdStreet = {
        _id: 'street_123',
        _rev: '1-test',
        type: 'street',
        ...streetData,
        status: 'available',
        adoptedBy: null,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };
      Street.create.mockResolvedValue(createdStreet);

      const response = await request(app)
        .post('/api/streets')
        .set('x-auth-token', token)
        .send(streetData)
        .expect(200);

      expect(response.body).toHaveProperty('name', streetData.name);
      expect(response.body).toHaveProperty('location');
      expect(response.body.location).toHaveProperty('coordinates');
    });

    it('should not create street without authentication', async () => {
      const streetData = {
        name: 'Broadway',
        location: {
          type: 'Point',
          coordinates: [-73.989308, 40.756432]
        }
      };

      const response = await request(app)
        .post('/api/streets')
        .send(streetData)
        .expect(401);

      expect(response.body).toHaveProperty('msg', 'No token, authorization denied');
    });
  });

  describe('PUT /api/streets/adopt/:id', () => {
it('should adopt an available street', async () => {
      const { user, token } = await createTestUser();
      const street = await createTestStreet(user.id, {
        status: 'available',
        adoptedBy: null
      });

      // Create street object with populate and save methods
      const streetWithPopulate = {
        ...street,
        populate: jest.fn().mockResolvedValue(street),
        save: jest.fn().mockResolvedValue({
          ...street,
          status: 'adopted',
          adoptedBy: {
            userId: user.id,
            name: user.name,
            profilePicture: ''
          }
        })
      };

      // Mock Street.findById and couchdbService.getDocument to return available street
      Street.findById.mockResolvedValue(streetWithPopulate);
      couchdbService.getDocument.mockResolvedValue(street);
      
      // Mock User.findById to return user with no adopted streets
      User.findById.mockResolvedValue({
        _id: user.id,
        adoptedStreets: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        save: jest.fn().mockResolvedValue(true),
        toJSON: () => ({ 
          _id: user.id, 
          adoptedStreets: [],
          stats: {
            streetsAdopted: 0,
            tasksCompleted: 0,
            postsCreated: 0,
            eventsParticipated: 0,
            badgesEarned: 0
          }
        })
      });
      
      // Mock couchdbService.updateDocument for adoption update
      const updatedStreet = {
        ...street,
        status: 'adopted',
        adoptedBy: {
          userId: user.id,
          name: user.name,
          profilePicture: ''
        }
      };
      couchdbService.updateDocument.mockResolvedValue(updatedStreet);

      const response = await request(app)
        .put(`/api/streets/adopt/${street.id}`)
        .set('x-auth-token', token)
        .expect(200);

expect(response.body).toHaveProperty('street');
      expect(response.body.street).toHaveProperty('status', 'adopted');
      expect(response.body.street).toHaveProperty('adoptedBy');
      expect(response.body).toHaveProperty('pointsAwarded', 50);
      expect(response.body).toHaveProperty('newBalance', 50);
    });

    it('should not adopt an already adopted street', async () => {
      const { user, token } = await createTestUser();
      const street = await createTestStreet(user.id, {
        status: 'adopted',
        adoptedBy: user.id
      });

      // Mock Street.findById to return already adopted street
      Street.findById.mockResolvedValue(street);

      const response = await request(app)
        .put(`/api/streets/adopt/${street.id}`)
        .set('x-auth-token', token)
        .expect(400);

      expect(response.body).toHaveProperty('msg', 'Street already adopted');
    });

    it('should return 404 for non-existent street', async () => {
      const { token } = await createTestUser();
      const fakeId = '507f1f77bcf86cd799439011';

      // Mock Street.findById to return null (not found)
      Street.findById.mockResolvedValue(null);

      const response = await request(app)
        .put(`/api/streets/adopt/${fakeId}`)
        .set('x-auth-token', token)
        .expect(404);

      expect(response.body).toHaveProperty('msg', 'Street not found');
    });

    it('should not adopt street without authentication', async () => {
      const { user } = await createTestUser();
      const street = await createTestStreet(user.id);

      const response = await request(app)
        .put(`/api/streets/adopt/${street.id}`)
        .expect(401);

      expect(response.body).toHaveProperty('msg', 'No token, authorization denied');
    });
  });
});
