import { Writable } from 'stream';
const stdout = process.stdout as Writable & { _handle?: { setBlocking?: (v: boolean) => void } };
const stderr = process.stderr as Writable & { _handle?: { setBlocking?: (v: boolean) => void } };

if (stdout._handle && typeof stdout._handle.setBlocking === 'function') {
  stdout._handle.setBlocking(true);
}
if (stderr._handle && typeof stderr._handle.setBlocking === 'function') {
  stderr._handle.setBlocking(true);
}

import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import pool from './db';
import dotenv from 'dotenv';
import rateLimit from 'express-rate-limit';

dotenv.config();

if (process.env.NODE_ENV === 'production' && (!process.env.JWT_SECRET || process.env.JWT_SECRET === 'your_jwt_secret_here')) {
  console.error("FATAL: JWT_SECRET environment variable is not securely set in production!");
  process.exit(1);
}

const app = express();
const PORT = process.env.PORT || 5000;

const allowedOrigin = process.env.ALLOWED_ORIGIN || 'http://localhost:5173';
app.use(cors({
  origin: allowedOrigin
}));
app.use(express.json({ limit: '50mb' }));

// Rate limiter for authentication routes
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per window
  message: { error: 'Too many authentication attempts. Please try again later.' }
});

// Request logger middleware
app.use((req: Request, res: Response, next: NextFunction) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  if (req.body && Object.keys(req.body).length > 0) {
    const logBody = { ...req.body };
    if (logBody.password) logBody.password = '[REDACTED]';
    console.log('  Body:', JSON.stringify(logBody));
  }
  next();
});

interface UserPayload {
  id: number;
  username: string;
}

interface AuthRequest extends Request {
  user?: UserPayload;
}

// Middleware to verify JWT token
const authenticateToken = (req: AuthRequest, res: Response, next: NextFunction) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) return res.status(401).json({ error: 'No token provided' });
  
  jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret', (err, decoded) => {
    if (err) return res.status(403).json({ error: 'Invalid token' });
    req.user = decoded as UserPayload;
    next();
  });
};

// Registration
app.post('/api/register', authLimiter, async (req: Request, res: Response) => {
  try {
    const { username, email, password } = req.body;
    
    if (!username || !email || !password) {
      return res.status(400).json({ error: 'All fields are required' });
    }

    // Check if user exists
    const userExists = await pool.query('SELECT * FROM users WHERE email = $1 OR username = $2', [email, username]);
    if (userExists.rows.length > 0) {
      return res.status(400).json({ error: 'User already exists' });
    }

    // Hash password
    const salt = await bcrypt.genSalt(10);
    const passwordHash = await bcrypt.hash(password, salt);

    // Save to DB
    const newUser = await pool.query(
      'INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3) RETURNING id, username, email',
      [username, email, passwordHash]
    );

    res.status(201).json({ message: 'Registration successful', user: newUser.rows[0] });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Server error' });
  }
});

// Login
app.post('/api/login', authLimiter, async (req: Request, res: Response) => {
  try {
    const { email, password } = req.body;

    if (!email || !password) {
      return res.status(400).json({ error: 'Email and password are required' });
    }

    // Find user
    const user = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
    if (user.rows.length === 0) {
      return res.status(400).json({ error: 'Invalid credentials' });
    }

    // Check password
    const validPassword = await bcrypt.compare(password, user.rows[0].password_hash);
    if (!validPassword) {
      return res.status(400).json({ error: 'Invalid credentials' });
    }

    // Create token
    const token = jwt.sign(
      { id: user.rows[0].id, username: user.rows[0].username },
      process.env.JWT_SECRET || 'fallback_secret',
      { expiresIn: '24h' }
    );

    res.json({ token, user: { id: user.rows[0].id, username: user.rows[0].username, email: user.rows[0].email } });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Server error' });
  }
});

// Get user profile (protected route)
app.get('/api/profile', authenticateToken as express.RequestHandler, async (req: AuthRequest, res: Response) => {
  try {
    if (!req.user) return res.status(401).json({ error: 'Unauthorized' });
    const user = await pool.query('SELECT id, username, email, created_at FROM users WHERE id = $1', [req.user.id]);
    if (user.rows.length === 0) {
      return res.status(404).json({ error: 'User not found' });
    }
    res.json(user.rows[0]);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Server error' });
  }
});

// Verify token
app.get('/api/verify-token', authenticateToken as express.RequestHandler, (req: AuthRequest, res: Response) => {
  res.json({ valid: true, user: req.user });
});
app.post('/api/verify-token', authenticateToken as express.RequestHandler, (req: AuthRequest, res: Response) => {
  res.json({ valid: true, user: req.user });
});

interface BeanInput {
  id: string;
  name?: string;
  roastery?: string;
  tastingNotes?: string;
  roastType?: string;
  roastDate?: string;
  image?: string;
  updatedAt?: string;
  isDeleted?: boolean;
}

interface BrewLogInput {
  id: string;
  beanId: string;
  method?: string;
  grind?: string;
  waterTemp?: string;
  ratio?: string;
  yield?: string;
  time?: string;
  notes?: string;
  rating?: number;
  createdAt?: number;
  updatedAt?: string;
  isDeleted?: boolean;
}

// Sync data (beans and brew logs)
app.post('/api/sync', authenticateToken as express.RequestHandler, async (req: AuthRequest, res: Response) => {
  if (!req.user) return res.status(401).json({ error: 'Unauthorized' });
  const userId = req.user.id;
  const { lastSyncedAt, beans = [], brewLogs = [] } = req.body as {
    lastSyncedAt?: string;
    beans?: BeanInput[];
    brewLogs?: BrewLogInput[];
  };
  const serverTime = new Date().toISOString();

  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    // 1. Process incoming beans
    for (const bean of beans) {
      await client.query(`
        INSERT INTO beans (id, user_id, name, roastery, tasting_notes, roast_type, roast_date, image, updated_at, is_deleted)
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
        ON CONFLICT (id) DO UPDATE SET
          name = EXCLUDED.name,
          roastery = EXCLUDED.roastery,
          tasting_notes = EXCLUDED.tasting_notes,
          roast_type = EXCLUDED.roast_type,
          roast_date = EXCLUDED.roast_date,
          image = EXCLUDED.image,
          updated_at = EXCLUDED.updated_at,
          is_deleted = EXCLUDED.is_deleted
        WHERE (EXCLUDED.updated_at > beans.updated_at OR beans.user_id IS NULL)
          AND (beans.user_id = EXCLUDED.user_id OR beans.user_id IS NULL)
      `, [
        bean.id,
        userId,
        bean.name || '',
        bean.roastery || '',
        bean.tastingNotes || '',
        bean.roastType || '',
        bean.roastDate || '',
        bean.image || '',
        bean.updatedAt ? new Date(bean.updatedAt) : new Date(),
        bean.isDeleted || false
      ]);
    }

    // 2. Process incoming brew logs
    for (const log of brewLogs) {
      await client.query(`
        INSERT INTO brew_logs (id, user_id, bean_id, method, grind, water_temp, ratio, yield, time, notes, rating, created_at, updated_at, is_deleted)
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
        ON CONFLICT (id) DO UPDATE SET
          bean_id = EXCLUDED.bean_id,
          method = EXCLUDED.method,
          grind = EXCLUDED.grind,
          water_temp = EXCLUDED.water_temp,
          ratio = EXCLUDED.ratio,
          yield = EXCLUDED.yield,
          time = EXCLUDED.time,
          notes = EXCLUDED.notes,
          rating = EXCLUDED.rating,
          created_at = EXCLUDED.created_at,
          updated_at = EXCLUDED.updated_at,
          is_deleted = EXCLUDED.is_deleted
        WHERE (EXCLUDED.updated_at > brew_logs.updated_at OR brew_logs.user_id IS NULL)
          AND (brew_logs.user_id = EXCLUDED.user_id OR brew_logs.user_id IS NULL)
      `, [
        log.id,
        userId,
        log.beanId,
        log.method || '',
        log.grind || '',
        log.waterTemp || '',
        log.ratio || '',
        log.yield || '',
        log.time || '',
        log.notes || '',
        log.rating || 0,
        log.createdAt ? BigInt(log.createdAt) : BigInt(Date.now()),
        log.updatedAt ? new Date(log.updatedAt) : new Date(),
        log.isDeleted || false
      ]);
    }

    // 3. Fetch server changes since lastSyncedAt
    let beansQuery = `SELECT * FROM beans WHERE user_id = $1`;
    let logsQuery = `SELECT * FROM brew_logs WHERE user_id = $1`;
    const params: any[] = [userId];

    if (lastSyncedAt) {
      beansQuery += ` AND updated_at > $2`;
      logsQuery += ` AND updated_at > $2`;
      params.push(new Date(lastSyncedAt));
    }

    const serverBeans = await client.query(beansQuery, params);
    const serverLogs = await client.query(logsQuery, params);

    await client.query('COMMIT');

    // Map DB fields back to camelCase expected by the frontend
    const mappedBeans = serverBeans.rows.map(b => ({
      id: b.id,
      name: b.name,
      roastery: b.roastery,
      tastingNotes: b.tasting_notes,
      roastType: b.roast_type,
      roastDate: b.roast_date,
      image: b.image,
      updatedAt: b.updated_at instanceof Date ? b.updated_at.toISOString() : new Date(b.updated_at).toISOString(),
      isDeleted: b.is_deleted
    }));

    const mappedLogs = serverLogs.rows.map(l => ({
      id: l.id,
      beanId: l.bean_id,
      method: l.method,
      grind: l.grind,
      waterTemp: l.water_temp,
      ratio: l.ratio,
      yield: l.yield,
      time: l.time,
      notes: l.notes,
      rating: l.rating,
      createdAt: Number(l.created_at),
      updatedAt: l.updated_at instanceof Date ? l.updated_at.toISOString() : new Date(l.updated_at).toISOString(),
      isDeleted: l.is_deleted
    }));

    res.json({
      serverTime,
      beans: mappedBeans,
      brewLogs: mappedLogs
    });

  } catch (err) {
    await client.query('ROLLBACK');
    console.error('Sync failed', err);
    res.status(500).json({ error: 'Sync failed' });
  } finally {
    client.release();
  }
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
