/**
 * Models API Integration Tests
 * Covers model listing, upload validation, duplicate detection, and deletion
 * behavior through the live backend server.
 */
const request = require('supertest');
const crypto = require('crypto');
const app = require('../../../server');

describe('Models API - Comprehensive Tests', () => {
    describe('GET /api/models', () => {
        it('should return all models as array', async () => {
            const res = await request(app).get('/api/models');
            expect(res.status).toBe(200);
            expect(Array.isArray(res.body)).toBe(true);
        });

        it('should return models with correct schema', async () => {
            const res = await request(app).get('/api/models');
            if (res.body.length > 0) {
                const model = res.body[0];
                expect(model).toHaveProperty('id');
                expect(model).toHaveProperty('external_id');
                expect(model).toHaveProperty('file_hash');
                expect(model).toHaveProperty('has_annotations');
            }
        });

        it('should filter by annotated status - true', async () => {
            const res = await request(app).get('/api/models?annotated=true');
            expect(res.status).toBe(200);
            expect(Array.isArray(res.body)).toBe(true);
            
            if (res.body.length > 0) {
                res.body.forEach(model => {
                    // If filtered for annotated, check that has_annotations logic is correct
                    expect(typeof model.has_annotations).toBe('boolean');
                });
            }
        });

        it('should filter by annotated status - false', async () => {
            const res = await request(app).get('/api/models?annotated=false');
            expect(res.status).toBe(200);
            expect(Array.isArray(res.body)).toBe(true);
        });

        it('should handle invalid status filter gracefully', async () => {
            const res = await request(app).get('/api/models?annotated=maybe');
            // Should either ignore invalid param or handle it
            expect([200]).toContain(res.status);
            expect(Array.isArray(res.body)).toBe(true);
        });
    });

    describe('POST /api/models', () => {
        it('should reject request without modelFile', async () => {
            const res = await request(app)
                .post('/api/models')
                .field('modelId', 'test');
            expect(res.status).toBe(400);
            expect(res.body).toHaveProperty('error');
        });

        it('should reject duplicate model name uploads', async () => {
            const uniqueName = `dup-name-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;

            const firstObj = Buffer.from('mtllib first.mtl\nv 0 0 0\nf 1 1 1\n');
            const firstMtl = Buffer.from('newmtl mat\nmap_Kd first.jpg\n');
            const firstTexture = Buffer.from([0xff, 0xd8, 0xff, 0xd9]);
            const firstRes = await request(app)
                .post('/api/models')
                .field('modelId', uniqueName)
                .attach('modelFile', firstObj, 'first.obj')
                .attach('mtlFile', firstMtl, 'first.mtl')
                .attach('textureFile', firstTexture, 'first.jpg');

            expect(firstRes.status).toBe(201);

            const secondObj = Buffer.from('mtllib second.mtl\nv 1 0 0\nf 1 1 1\n');
            const secondMtl = Buffer.from('newmtl mat\nmap_Kd second.jpg\n');
            const secondTexture = Buffer.from([0xff, 0xd8, 0xff, 0xd9, 0x00]);
            const secondRes = await request(app)
                .post('/api/models')
                .field('modelId', uniqueName)
                .attach('modelFile', secondObj, 'second.obj')
                .attach('mtlFile', secondMtl, 'second.mtl')
                .attach('textureFile', secondTexture, 'second.jpg');

            expect(secondRes.status).toBe(409);
            expect(secondRes.body.errorCode).toBe('modelNameAlreadyExists');

            const createdId = firstRes.body?.modelId;
            if (createdId) {
                await request(app).delete(`/api/models/${createdId}`);
            }
        });

        it('should reject upload when full 3-file set is not provided', async () => {
            const res = await request(app)
                .post('/api/models')
                .field('modelId', `missing-set-${Date.now()}`)
                .attach('modelFile', Buffer.from('v 0 0 0\nf 1 1 1\n'), 'incomplete.obj');

            expect(res.status).toBe(400);
            expect(res.body.error).toBe('requireCompleteModelSet');
        });

        it('should reject upload when mtlFile is missing', async () => {
            // This test just validates endpoint handles optional files
            const res = await request(app)
                .post('/api/models')
                .field('modelId', 'test-no-mtl');
            // Should fail because full 3-file set is required
            expect(res.status).toBe(400);
        });

        it('should validate file size limits', async () => {
            // Attempting to upload oversized file should fail
            const largeBuffer = Buffer.alloc(60 * 1024 * 1024); // 60MB
            const res = await request(app)
                .post('/api/models')
                .field('modelId', 'large')
                .attach('modelFile', largeBuffer, 'large.obj');
            
            expect(res.status).toBeGreaterThanOrEqual(400);
        });
    });

    describe('DELETE /api/models/:id', () => {
        it('should keep model in database when annotation is deleted', async () => {
            const uniqueName = `keep-model-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
            const obj = Buffer.from('mtllib keep.mtl\nv 0 0 0\nf 1 1 1\n');
            const mtl = Buffer.from('newmtl mat\nmap_Kd keep.jpg\n');
            const texture = Buffer.from([0xff, 0xd8, 0xff, 0xd9, 0x01]);
            const fileHash = crypto.createHash('sha256').update(obj).digest('hex');

            const modelRes = await request(app)
                .post('/api/models')
                .field('modelId', uniqueName)
                .attach('modelFile', obj, 'keep.obj')
                .attach('mtlFile', mtl, 'keep.mtl')
                .attach('textureFile', texture, 'keep.jpg');

            expect(modelRes.status).toBe(201);
            const createdModelId = modelRes.body?.modelId;
            expect(createdModelId).toBeTruthy();

            const atlasesRes = await request(app).get('/api/atlases');
            expect(atlasesRes.status).toBe(200);
            const atlasId = atlasesRes.body[0]?.id;
            expect(atlasId).toBeTruthy();

            const annotationRes = await request(app)
                .post('/annotations')
                .field('atlasId', atlasId)
                .field('modelId', uniqueName)
                .field('annotatorName', 'Keep Model User')
                .field('fileHash', fileHash)
                .field('values', JSON.stringify({}));

            expect(annotationRes.status).toBe(200);
            const createdAnnotationId = annotationRes.body?.annotationId;
            expect(createdAnnotationId).toBeTruthy();

            const deleteAnnotationRes = await request(app).delete(`/annotations/${createdAnnotationId}`);
            expect(deleteAnnotationRes.status).toBe(200);

            const modelsRes = await request(app).get('/api/models');
            expect(modelsRes.status).toBe(200);
            expect(modelsRes.body.some(m => m.id === createdModelId)).toBe(true);

            // Cleanup model at the end of test
            await request(app).delete(`/api/models/${createdModelId}`);
        });

        it('should delete model together with its annotations', async () => {
            const uniqueName = `delete-with-annotations-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
            const annotatorName = 'Delete Test User';

            const obj = Buffer.from('mtllib model.mtl\nv 0 0 0\nf 1 1 1\n');
            const mtl = Buffer.from('newmtl mat\nmap_Kd model.jpg\n');
            const texture = Buffer.from([0xff, 0xd8, 0xff, 0xd9, 0x00]);
            const fileHash = crypto.createHash('sha256').update(obj).digest('hex');

            const modelRes = await request(app)
                .post('/api/models')
                .field('modelId', uniqueName)
                .attach('modelFile', obj, 'model.obj')
                .attach('mtlFile', mtl, 'model.mtl')
                .attach('textureFile', texture, 'model.jpg');

            expect(modelRes.status).toBe(201);
            const createdModelId = modelRes.body?.modelId;
            expect(createdModelId).toBeTruthy();

            const atlasesRes = await request(app).get('/api/atlases');
            expect(atlasesRes.status).toBe(200);
            expect(Array.isArray(atlasesRes.body)).toBe(true);
            expect(atlasesRes.body.length).toBeGreaterThan(0);
            const atlasId = atlasesRes.body[0].id;

            const annotationRes = await request(app)
                .post('/annotations')
                .field('atlasId', atlasId)
                .field('modelId', uniqueName)
                .field('annotatorName', annotatorName)
                .field('fileHash', fileHash)
                .field('values', JSON.stringify({}));

            expect(annotationRes.status).toBe(200);

            const deleteRes = await request(app).delete(`/api/models/${createdModelId}`);
            expect(deleteRes.status).toBe(200);

            const modelsResAfterDelete = await request(app).get('/api/models');
            expect(modelsResAfterDelete.status).toBe(200);
            expect(modelsResAfterDelete.body.some(m => m.id === createdModelId)).toBe(false);

            const annotationsResAfterDelete = await request(app).get('/annotations');
            expect(annotationsResAfterDelete.status).toBe(200);
            const anns = annotationsResAfterDelete.body.annotations || [];
            expect(anns.some(a => a.annotatorName === annotatorName)).toBe(false);
        });

        it('should handle non-existent model gracefully', async () => {
            const validUuid = '123e4567-e89b-12d3-a456-426614174000';
            const res = await request(app).delete(`/api/models/${validUuid}`);
            // Should return 404 or 500 depending on implementation
            expect([404, 500]).toContain(res.status);
        });

        it('should reject invalid uuid format', async () => {
            const res = await request(app).delete('/api/models/not-a-uuid');
            expect(res.status).toBe(400);
        });

        it('should validate model exists before deletion', async () => {
            const res = await request(app).delete('/api/models/00000000-0000-0000-0000-000000000000');
            expect([400, 404, 500]).toContain(res.status);
        });
    });
});