import { FastifyInstance } from 'fastify';
import { promises as fs } from 'fs';
import path from 'path';
import crypto from 'crypto';
import sharp from 'sharp';

// Storage base path - configurable via env
const STORAGE_PATH = process.env.STORAGE_PATH || '/tmp/ca-grow-ops-manager/photos';

// Image size configurations per spec
const IMAGE_SIZES = {
    thumb: { width: 200, height: 200, quality: 80 },
    medium: { width: 800, height: 800, quality: 85 },
    full: { width: 1920, height: 1920, quality: 90 }
} as const;

function generatePhotoId(): string {
    return `photo_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
}

async function processImage(buffer: Buffer, size: keyof typeof IMAGE_SIZES): Promise<Buffer> {
    const config = IMAGE_SIZES[size];
    return sharp(buffer)
        .resize(config.width, config.height, {
            fit: 'inside',
            withoutEnlargement: true
        })
        .webp({ quality: config.quality })
        .toBuffer();
}

async function getImageMetadata(buffer: Buffer): Promise<{ width: number; height: number; format: string }> {
    const metadata = await sharp(buffer).metadata();
    return {
        width: metadata.width || 0,
        height: metadata.height || 0,
        format: metadata.format || 'unknown'
    };
}

export async function uploadRoutes(server: FastifyInstance) {
    // Register multipart support
    await server.register(import('@fastify/multipart'), {
        limits: {
            fileSize: 10 * 1024 * 1024, // 10MB max (before compression)
        },
    });

    // Auth middleware
    server.addHook('onRequest', async (request) => {
        try {
            await request.jwtVerify();
        } catch (err) {
            throw err;
        }
    });

    /**
     * POST /upload/photo
     * Upload a photo with automatic compression to 3 sizes (thumb, medium, full)
     * Returns URLs for all sizes in WebP format
     */
    server.post('/photo', async (request, reply) => {
        try {
            const data = await request.file();

            if (!data) {
                return reply.status(400).send({ error: 'No file uploaded' });
            }

            // Validate file type
            const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/heic', 'image/heif'];
            if (!allowedTypes.includes(data.mimetype)) {
                return reply.status(400).send({
                    error: 'Invalid file type',
                    allowed: ['JPEG', 'PNG', 'WebP', 'HEIC']
                });
            }

            const buffer = await data.toBuffer();
            const photoId = generatePhotoId();
            const date = new Date();

            // Get original metadata
            const metadata = await getImageMetadata(buffer);

            // Build path: /photos/{year}/{month}/{day}/{photoId}/
            const datePath = `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}`;
            const photoPath = path.join(datePath, photoId);
            const basePath = path.join(STORAGE_PATH, photoPath);

            // Ensure directory exists
            await fs.mkdir(basePath, { recursive: true });

            // Process and save all sizes
            const urls: Record<string, string> = {};
            const sizes: Record<string, number> = {};

            for (const [sizeName, config] of Object.entries(IMAGE_SIZES)) {
                const processedBuffer = await processImage(buffer, sizeName as keyof typeof IMAGE_SIZES);
                const filename = `${sizeName}.webp`;
                const filepath = path.join(basePath, filename);

                await fs.writeFile(filepath, processedBuffer);

                urls[sizeName] = `/photos/${photoPath}/${filename}`;
                sizes[sizeName] = processedBuffer.length;
            }

            // Calculate compression savings
            const originalSize = buffer.length;
            const compressedSize = sizes.full;
            const savings = Math.round((1 - compressedSize / originalSize) * 100);

            return {
                success: true,
                photoId,
                urls,
                metadata: {
                    originalSize,
                    originalFormat: metadata.format,
                    originalDimensions: { width: metadata.width, height: metadata.height },
                    compressedSizes: sizes,
                    savingsPercent: savings,
                    format: 'webp'
                },
                uploadedAt: new Date().toISOString()
            };
        } catch (error: any) {
            server.log.error(error);
            return reply.status(500).send({
                error: 'Upload failed',
                message: error.message
            });
        }
    });

    /**
     * POST /upload/photos
     * Bulk upload multiple photos
     */
    server.post('/photos', async (request, reply) => {
        try {
            const parts = request.parts();
            const results: any[] = [];
            const errors: any[] = [];

            for await (const part of parts) {
                if (part.type === 'file') {
                    try {
                        const buffer = await part.toBuffer();
                        const photoId = generatePhotoId();
                        const date = new Date();

                        const datePath = `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}`;
                        const photoPath = path.join(datePath, photoId);
                        const basePath = path.join(STORAGE_PATH, photoPath);

                        await fs.mkdir(basePath, { recursive: true });

                        const urls: Record<string, string> = {};

                        for (const sizeName of Object.keys(IMAGE_SIZES)) {
                            const processedBuffer = await processImage(buffer, sizeName as keyof typeof IMAGE_SIZES);
                            const filename = `${sizeName}.webp`;
                            const filepath = path.join(basePath, filename);

                            await fs.writeFile(filepath, processedBuffer);
                            urls[sizeName] = `/photos/${photoPath}/${filename}`;
                        }

                        results.push({
                            photoId,
                            filename: part.filename,
                            urls
                        });
                    } catch (err: any) {
                        errors.push({
                            filename: part.filename,
                            error: err.message
                        });
                    }
                }
            }

            return {
                success: true,
                uploaded: results.length,
                failed: errors.length,
                results,
                errors: errors.length > 0 ? errors : undefined
            };
        } catch (error: any) {
            server.log.error(error);
            return reply.status(500).send({ error: 'Bulk upload failed', message: error.message });
        }
    });

    /**
     * GET /upload/photo/*
     * Serve a photo (proxy for auth)
     */
    server.get('/photo/*', async (request, reply) => {
        try {
            const { '*': photoPath } = request.params as { '*': string };
            const fullPath = path.join(STORAGE_PATH, photoPath);

            // Security: prevent path traversal
            const resolvedPath = path.resolve(fullPath);
            const resolvedBase = path.resolve(STORAGE_PATH);
            if (!resolvedPath.startsWith(resolvedBase)) {
                return reply.status(403).send({ error: 'Forbidden' });
            }

            try {
                const stat = await fs.stat(fullPath);
                const file = await fs.readFile(fullPath);

                const ext = path.extname(fullPath).toLowerCase();
                const contentType = ext === '.png' ? 'image/png' :
                    ext === '.webp' ? 'image/webp' :
                        ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' :
                            'application/octet-stream';

                reply.header('Content-Type', contentType);
                reply.header('Content-Length', stat.size);
                reply.header('Cache-Control', 'public, max-age=31536000, immutable');
                reply.header('ETag', `"${stat.mtime.getTime().toString(16)}"`);

                return reply.send(file);
            } catch {
                return reply.status(404).send({ error: 'Photo not found' });
            }
        } catch (error: any) {
            server.log.error(error);
            return reply.status(500).send({ error: 'Failed to serve photo' });
        }
    });

    /**
     * DELETE /upload/photo/:photoId
     * Delete a photo and all its sizes
     */
    server.delete('/photo/:photoId', async (request, reply) => {
        try {
            const { photoId } = request.params as { photoId: string };

            // Find the photo directory
            // Photos are stored as: /photos/{year}/{month}/{day}/{photoId}/
            // We need to search for it

            // Parse date from photoId (format: photo_TIMESTAMP_HASH)
            const parts = photoId.split('_');
            if (parts.length < 2) {
                return reply.status(400).send({ error: 'Invalid photo ID format' });
            }

            const timestamp = parseInt(parts[1]);
            const date = new Date(timestamp);

            if (isNaN(date.getTime())) {
                return reply.status(400).send({ error: 'Invalid photo timestamp' });
            }

            const year = date.getFullYear();
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');

            let photoDir = path.join(STORAGE_PATH, `${year}/${month}/${day}`, photoId);

            try {
                await fs.access(photoDir);
            } catch {
                return reply.status(404).send({ error: 'Photo not found' });
            }

            // Security check
            const resolvedPath = path.resolve(photoDir);
            const resolvedBase = path.resolve(STORAGE_PATH);
            if (!resolvedPath.startsWith(resolvedBase)) {
                return reply.status(403).send({ error: 'Forbidden' });
            }

            // Delete all files in the directory
            const files = await fs.readdir(photoDir);
            for (const file of files) {
                await fs.unlink(path.join(photoDir, file));
            }

            // Remove the directory
            await fs.rmdir(photoDir);

            return {
                success: true,
                deleted: photoId,
                filesRemoved: files.length
            };
        } catch (error: any) {
            server.log.error(error);
            return reply.status(500).send({ error: 'Failed to delete photo' });
        }
    });

    /**
     * GET /upload/stats
     * Get storage statistics
     */
    server.get('/stats', async (request, reply) => {
        try {
            const stats = {
                storagePath: STORAGE_PATH,
                sizes: IMAGE_SIZES,
                format: 'webp'
            };

            // Try to get disk usage
            try {
                const { stdout } = await import('child_process').then(cp =>
                    new Promise<{ stdout: string }>((resolve, reject) => {
                        cp.exec(`du -sh ${STORAGE_PATH}`, (err, stdout) => {
                            if (err) reject(err);
                            else resolve({ stdout });
                        });
                    })
                );
                (stats as any).diskUsage = stdout.trim().split('\t')[0];
            } catch {
                (stats as any).diskUsage = 'unknown';
            }

            return stats;
        } catch (error: any) {
            server.log.error(error);
            return reply.status(500).send({ error: 'Failed to get stats' });
        }
    });
}
