import express from 'express';
import path from 'path';
import dotenv from 'dotenv';
import rateLimit from 'express-rate-limit';
import morgan from 'morgan';
import type { Database } from 'sql.js';
import { getDatabase, saveDatabase } from './db/database.js';
import { createRecipeRoutes } from './routes/recipes.js';
import { createTagRoutes } from './routes/tags.js';
import { createImportRoutes } from './routes/import.js';
import { createImportLocalRoutes } from './routes/importLocal.js';
import { createHarnessRoutes } from './routes/harness.js';
import { logInfo, logError } from './logger.js';
import { ZodError } from 'zod';

// Load environment variables
dotenv.config();

const app = express();
const port = parseInt(process.env.PORT || '3000', 10);
const DB_PATH = process.env.DATABASE_PATH || 'data/recipes.db';
const ALLOWED_ORIGIN = process.env.ALLOWED_ORIGIN || '*';
const API_KEY = process.env.API_KEY; // if set, will be required for write methods

// Rate limiter for import endpoints
const importRateLimit = rateLimit({
  windowMs: parseInt(process.env.IMPORT_RATE_LIMIT_WINDOW_MS || '60000', 10),
  max: parseInt(process.env.IMPORT_RATE_LIMIT_MAX_REQUESTS || '10', 10),
  message: { success: false, error: 'Too many import requests, please try again later.' },
  standardHeaders: true,
  legacyHeaders: false,
});

// Database dirty tracking for smarter saves
let dbDirty = true; // start dirty to ensure initial save
const originalSave = saveDatabase;
const dirtySave = (dbPath: string = 'data/recipes.db') => {
  if (dbDirty) {
    originalSave(dbPath);
    dbDirty = false;
  }
};
// Monkey-patch repositories to set dirty flag on mutations
// (Will patch specific db.run calls later)
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Serve static images from data/images directory
app.use('/images', express.static(path.join(process.cwd(), 'data', 'images')));

// CORS headers (configurable)
app.use((req, res, next) => {
  const origin = req.headers.origin as string | undefined;
  if (ALLOWED_ORIGIN === '*') {
    res.header('Access-Control-Allow-Origin', '*');
  } else if (origin && origin === ALLOWED_ORIGIN) {
    res.header('Access-Control-Allow-Origin', origin);
  }
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  if (req.method === 'OPTIONS') {
    res.sendStatus(200);
  } else {
    next();
  }
});

// API key authentication middleware (only enforce if API_KEY is configured)
const requireApiKeyForWrite: express.RequestHandler = (req, res, next) => {
  const safeMethods = ['GET', 'HEAD', 'OPTIONS'];
  if (safeMethods.includes(req.method)) {
    return next();
  }
  if (!API_KEY) {
    return next(); // no auth configured
  }
  const key = req.headers['x-api-key'] ?? req.query['api_key'] as string | undefined;
  if (!key || key !== API_KEY) {
    return res.status(401).json({ success: false, error: 'Invalid or missing API key' });
  }
  next();
};

// Health check endpoint
app.get('/api/health', (_req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// Health check endpoint
app.get('/', (req, res) => {
  res.json({
    success: true,
    message: 'Recipe Manager API is running',
    version: '0.1.0',
  });
});

// Request logging (morgan) – enabled unless explicitly disabled
if (process.env.LOG_LEVEL !== 'silent') {
  app.use(morgan('dev'));
}

// Initialize database and routes
async function startServer() {
  try {
    const db = await getDatabase(DB_PATH);
    
    // Patch db.run to set dirty flag on any write
    const originalRun = db.run.bind(db);
    db.run = function(this: any, sql: string, params?: any[]): Database {
      dbDirty = true;
      return originalRun(sql, params);
    };
    
    // Mount API routes (write routes protected by API key if configured)
    app.use('/api/recipes', requireApiKeyForWrite, createRecipeRoutes(db));
    app.use('/api/tags', requireApiKeyForWrite, createTagRoutes(db));
    app.use('/api/import', importRateLimit, requireApiKeyForWrite, createImportRoutes());
    app.use('/api/import/local', importRateLimit, requireApiKeyForWrite, createImportLocalRoutes());
    // Harness routes are internal; keep unprotected but consider restricting in production
    app.use('/api/harness', createHarnessRoutes(process.cwd()));
    
    // Save database periodically (only if dirty)
    setInterval(() => {
      try {
        dirtySave(DB_PATH);
      } catch (error) {
        logError('Error saving database:', error);
      }
    }, 5000);

    // Global error handler
    app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
      logError('Unhandled error:', err);

      // Zod validation errors → 400
      if (err.constructor?.name === 'ZodError' || (err.errors && Array.isArray(err.errors))) {
        const msg = err.errors?.[0]?.message || 'Invalid request data';
        return res.status(400).json({ success: false, error: msg });
      }

      // Known service errors (duplicate, empty) → 400
      if (err.message && (err.message.includes('already exists') || err.message.includes('cannot be empty'))) {
        return res.status(400).json({ success: false, error: err.message });
      }

      // Map known service/import errors to proper status codes
      const msg = (err.message || '').toLowerCase();
      if (msg.includes('file too large') || err.status === 413) {
        return res.status(413).json({ success: false, error: 'File too large' });
      }
      if (msg.includes('timeout') || msg.includes('etimedout') || err.status === 504) {
        return res.status(504).json({ success: false, error: 'Gateway timeout' });
      }
      if (msg.includes('network') || msg.includes('enetunreach') || msg.includes('bad gateway') || err.status === 502) {
        return res.status(502).json({ success: false, error: 'Bad gateway' });
      }
      if (msg.includes('unsupported') || msg.includes('content type') || err.status === 415) {
        return res.status(415).json({ success: false, error: 'Unsupported media type' });
      }

      // Default
      res.status(500).json({ success: false, error: 'Internal server error' });
    });

    // Save database on exit
    process.on('SIGINT', () => {
      logInfo('\nSaving database before exit...');
      saveDatabase(DB_PATH);
      process.exit(0);
    });
    
    process.on('SIGTERM', () => {
      logInfo('\nSaving database before exit...');
      saveDatabase(DB_PATH);
      process.exit(0);
    });
    
    app.listen(port, () => {
      logInfo(`✓ Recipe Manager API running on http://localhost:${port}`);
      logInfo(`✓ Database: ${DB_PATH}`);
      logInfo(`✓ Static images: http://localhost:${port}/images`);
      logInfo(`✓ Endpoints:`);
      logInfo(`  Recipes:`);
      logInfo(`    GET    /api/recipes       - List recipes`);
      logInfo(`    GET    /api/recipes/:id   - Get recipe by ID`);
      logInfo(`    POST   /api/recipes       - Create recipe`);
      logInfo(`    PUT    /api/recipes/:id   - Update recipe`);
      logInfo(`    DELETE /api/recipes/:id   - Delete recipe`);
      logInfo(`  Tags:`);
      logInfo(`    GET    /api/tags                      - List tags`);
      logInfo(`    POST   /api/tags                      - Create tag`);
      logInfo(`    PUT    /api/tags/:id                  - Update tag`);
      logInfo(`    DELETE /api/tags/:id                  - Delete tag`);
      logInfo(`    GET    /api/tags/recipes/:id/tags     - Get recipe tags`);
      logInfo(`    POST   /api/tags/recipes/:id/tags     - Assign tag`);
      logInfo(`    DELETE /api/tags/recipes/:id/tags/:id - Remove tag`);
      logInfo(`  Import:`);
      logInfo(`    POST   /api/import/url                - Import recipe foundation data from URL`);
      logInfo(`    POST   /api/import/local              - Import recipes from local files (.html/.txt)`);
      logInfo(`  Harness:`);
      logInfo(`    GET    /api/harness/status            - Mission Control progress/status feed`);
    });
  } catch (error) {
    logError('Failed to start server:', error);
    process.exit(1);
  }
}

startServer();
