const express = require('express');
const bodyParser = require('body-parser');
const { GoogleGenerativeAI } = require('@google/generative-ai');
const { createClient } = require('redis');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 8080;
const API_KEY = process.env.GEMINI_API_KEY;
const AUTH_TOKEN = process.env.PROXY_AUTH_TOKEN;

// Rate Limiting Configuration
const DAILY_LIMIT = parseInt(process.env.DAILY_LIMIT || '50', 10);
const REDIS_HOST = process.env.REDIS_HOST || 'localhost';
const REDIS_PORT = parseInt(process.env.REDIS_PORT || '6379', 10);

// Redis Client Setup
const redisClient = createClient({
    socket: {
        host: REDIS_HOST,
        port: REDIS_PORT
    }
});

redisClient.on('error', (err) => {
    console.error('[Redis] Connection Error:', err);
});

redisClient.on('connect', () => {
    console.log(`[Redis] Connected to ${REDIS_HOST}:${REDIS_PORT}`);
});

// Initialize Redis connection
(async () => {
    try {
        await redisClient.connect();
    } catch (err) {
        console.error('[Redis] Failed to connect:', err);
        process.exit(1); // Exit if Redis is unavailable
    }
})();

// Authentication Middleware (skip for /health)
function authMiddleware(req, res, next) {
    if (!AUTH_TOKEN) {
        // If no token configured, skip auth (backward compatibility)
        console.warn('[Auth] WARNING: PROXY_AUTH_TOKEN is not set. Authentication disabled.');
        return next();
    }

    const authHeader = req.headers['authorization'];
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        console.log(`[Auth] Rejected: Missing or invalid Authorization header`);
        return res.status(401).json({ success: false, error: 'Authentication required' });
    }

    const token = authHeader.substring(7); // Remove 'Bearer ' prefix
    if (token !== AUTH_TOKEN) {
        console.log(`[Auth] Rejected: Invalid token`);
        return res.status(403).json({ success: false, error: 'Invalid authentication token' });
    }

    next();
}

// Global middleware: Body parser first, then auth (skip /health)
app.use(bodyParser.json({ limit: '10mb' }));
app.use((req, res, next) => {
    if (req.path === '/health') return next();
    authMiddleware(req, res, next);
});

// Gemini Client with JSON response configuration
const genAI = new GoogleGenerativeAI(API_KEY);
const model = genAI.getGenerativeModel({
    model: "gemini-2.5-flash", // Flutter側(gemini_service.dart)と統一
    generationConfig: {
        responseMimeType: "application/json", // Force JSON-only output
        temperature: 0.2, // チャート一貫性向上のため（Flutter側と統一）
    }
});

// Helper: Get Today's Date String (YYYY-MM-DD)
function getTodayString() {
    return new Date().toISOString().split('T')[0];
}

// Helper: Check & Update Rate Limit (Redis-based)
async function checkRateLimit(deviceId) {
    const today = getTodayString();
    const redisKey = `usage:${deviceId}:${today}`;

    try {
        // Get current usage count
        const currentCount = await redisClient.get(redisKey);
        const count = currentCount ? parseInt(currentCount, 10) : 0;
        const remaining = DAILY_LIMIT - count;

        return {
            allowed: remaining > 0,
            current: count,
            limit: DAILY_LIMIT,
            remaining: remaining,
            redisKey: redisKey
        };
    } catch (err) {
        console.error('[Redis] Error checking rate limit:', err);
        // Fallback: deny request if Redis is down
        return {
            allowed: false,
            current: 0,
            limit: DAILY_LIMIT,
            remaining: 0,
            error: 'Rate limit check failed'
        };
    }
}

// Helper: Increment Usage Count (Redis-based)
async function incrementUsage(deviceId) {
    const today = getTodayString();
    const redisKey = `usage:${deviceId}:${today}`;

    try {
        // Increment count
        const newCount = await redisClient.incr(redisKey);

        // Set expiration to end of day (86400 seconds = 24 hours)
        const now = new Date();
        const midnight = new Date(now);
        midnight.setHours(24, 0, 0, 0);
        const secondsUntilMidnight = Math.floor((midnight - now) / 1000);

        await redisClient.expire(redisKey, secondsUntilMidnight);

        return newCount;
    } catch (err) {
        console.error('[Redis] Error incrementing usage:', err);
        throw err;
    }
}

// API Endpoint (authentication enforced by global middleware)
app.post('/analyze', async (req, res) => {
    const { device_id, images, prompt } = req.body;

    if (!device_id) {
        return res.status(400).json({ success: false, error: 'Device ID is required' });
    }

    try {
        // 1. Check Rate Limit (Redis-based)
        const limitStatus = await checkRateLimit(device_id);
        if (!limitStatus.allowed) {
            console.log(`[Limit Reached] Device: ${device_id} (${limitStatus.current}/${limitStatus.limit})`);
            return res.status(429).json({
                success: false,
                error: limitStatus.error || 'Daily limit reached',
                usage: { today: limitStatus.current, limit: limitStatus.limit }
            });
        }

        console.log(`[Request] Device: ${device_id} | Images: ${images ? images.length : 0} | Current: ${limitStatus.current}/${limitStatus.limit}`);

        // 2. Prepare Gemini Request
        // Base64 images to GenerativeContentBlob
        const imageParts = (images || []).map(base64 => ({
            inlineData: {
                data: base64,
                mimeType: "image/jpeg"
            }
        }));

        const result = await model.generateContent([prompt, ...imageParts]);
        const response = await result.response;
        const text = response.text();

        // 3. Parse JSON from Markdown (e.g. ```json ... ```)
        console.log(`[Debug] Gemini raw response (first 200 chars): ${text.substring(0, 200)}`);

        const jsonMatch = text.match(/```json\n([\s\S]*?)\n```/) || text.match(/```([\s\S]*?)```/);
        let jsonData;

        if (jsonMatch) {
            console.log('[Debug] Found JSON in code block');
            jsonData = JSON.parse(jsonMatch[1]);
        } else {
            // Try parsing raw text if no code blocks
            console.log('[Debug] Attempting to parse raw text as JSON');
            try {
                jsonData = JSON.parse(text);
            } catch (parseError) {
                console.error('[Error] JSON parse failed. Raw response:', text);
                throw new Error(`Failed to parse Gemini response as JSON. Response starts with: ${text.substring(0, 100)}`);
            }
        }

        // 4. Increment Usage (Redis-based)
        const newCount = await incrementUsage(device_id);
        console.log(`[Success] Device: ${device_id} | New Count: ${newCount}/${DAILY_LIMIT}`);

        // 5. Send Response
        res.json({
            success: true,
            data: jsonData,
            usage: {
                today: newCount,
                limit: DAILY_LIMIT
            }
        });

    } catch (error) {
        console.error('[Error] Gemini API or Redis Error:', error);
        res.status(500).json({
            success: false,
            error: error.message || 'Internal Server Error'
        });
    }
});

// Health Check
app.get('/health', (req, res) => {
    res.send('OK');
});

// Start Server
app.listen(PORT, '0.0.0.0', () => {
    console.log(`Proxy Server running on port ${PORT}`);
    if (!API_KEY) console.error('WARNING: GEMINI_API_KEY is not set!');
    if (!AUTH_TOKEN) console.error('WARNING: PROXY_AUTH_TOKEN is not set! Authentication is disabled.');
    else console.log('Authentication: Bearer Token enabled');
});
