/**
 * Database Connection
 * Falls back to mock database if MongoDB is not available
 */

import mongoose from 'mongoose';
import mockDB from './mockDatabase';

function getMongoURI(): string {
  // Explicit URI wins; only default to localhost when nothing is configured.
  return process.env.MONGODB_URI || 'mongodb://localhost:27017/mengo';
}

/**
 * Whether the in-memory mock database may be used as a fallback.
 *
 * The mock stores everything in a per-process Map: data is lost on restart and
 * is never shared between environments. It must NEVER stand in for a real
 * database, otherwise saved settings/credentials silently vanish. Rules:
 *   - Production                → never.
 *   - ALLOW_MOCK_DB=true        → explicit opt-in (local dev only).
 *   - Otherwise                 → only when MONGODB_URI was NOT explicitly set
 *                                 (a bare `npm run dev` with no database).
 * A MONGODB_URI that IS set but unreachable is a fatal misconfiguration, not a
 * reason to silently drop to an ephemeral store.
 */
function mockAllowedOnFailure(): boolean {
  if (process.env.NODE_ENV === 'production') return false;
  if (process.env.ALLOW_MOCK_DB === 'true') return true;
  return !process.env.MONGODB_URI;
}

let useMock = false;

// Connection options
const options: mongoose.ConnectOptions = {
  maxPoolSize: 10,
  serverSelectionTimeoutMS: 30000,
  connectTimeoutMS: 30000,
  socketTimeoutMS: 120000,
};

const MAX_CONNECT_ATTEMPTS = 5;

/**
 * Connect to MongoDB. Retries a few times, then EITHER fails loudly (when a real
 * database is configured) OR — only in the explicitly-allowed dev case — falls
 * back to the in-memory mock. It never silently degrades a configured database
 * to an ephemeral store, so persistence problems surface immediately instead of
 * masquerading as "saved".
 */
export const connectDatabase = async (): Promise<void> => {
  const MONGODB_URI = getMongoURI();
  console.log(`📄 MONGODB_URI: ${MONGODB_URI.replace(/:([^@]{4})[^@]+@/, ':****@')}`);

  let lastError: any;
  for (let attempt = 1; attempt <= MAX_CONNECT_ATTEMPTS; attempt++) {
    try {
      await mongoose.connect(MONGODB_URI, options);
      useMock = false;
      console.log('✅ Connected to MongoDB');

      mongoose.connection.on('error', (err) => {
        console.error('MongoDB connection error:', err);
      });
      // Do NOT switch to the mock on a transient disconnect — mongoose
      // auto-reconnects, and swapping in an empty in-memory store would lose
      // writes. Operations during a blip surface as errors instead.
      mongoose.connection.on('disconnected', () => {
        console.warn('⚠️ MongoDB disconnected — waiting for mongoose to reconnect...');
      });
      mongoose.connection.on('reconnected', () => {
        console.log('✅ MongoDB reconnected');
      });
      return;
    } catch (error: any) {
      lastError = error;
      console.error(`❌ MongoDB connection attempt ${attempt}/${MAX_CONNECT_ATTEMPTS} failed:`, error.message || error);
      if (attempt < MAX_CONNECT_ATTEMPTS) {
        const delay = Math.min(attempt * 3000, 15000);
        console.warn(`   Retrying in ${delay / 1000}s...`);
        await new Promise((resolve) => setTimeout(resolve, delay));
      }
    }
  }

  // Every attempt failed.
  if (mockAllowedOnFailure()) {
    console.warn('');
    console.warn('════════════════════════════════════════════════════════════════════');
    console.warn('⚠️  MONGODB UNAVAILABLE — using in-memory mock database (dev fallback).');
    console.warn('    DATA IS NOT PERSISTED and is lost on restart. Set a reachable');
    console.warn('    MONGODB_URI to persist data. (This fallback is disabled in');
    console.warn('    production and whenever MONGODB_URI is explicitly set.)');
    console.warn('════════════════════════════════════════════════════════════════════');
    console.warn('');
    useMock = true;
    return;
  }

  // A real database was configured but is unreachable — fail loudly rather than
  // silently running on an ephemeral store that loses data and never syncs.
  console.error('');
  console.error('════════════════════════════════════════════════════════════════════');
  console.error('❌ FATAL: Could not connect to MongoDB after multiple attempts.');
  console.error('   Refusing to start on an in-memory database — that silently loses');
  console.error('   all saved data (settings, platform credentials, publications, ...)');
  console.error('   on every restart and never syncs between environments.');
  console.error('   Fix MONGODB_URI / connectivity:');
  console.error('     • Prefer the NON-SRV string:');
  console.error('       mongodb://host1:27017,host2:27017,host3:27017/<db>?replicaSet=...&authSource=admin&tls=true');
  console.error('       — mongodb+srv:// needs a DNS SRV lookup that often times out on');
  console.error('       servers or behind firewalls, which is what triggers this.');
  console.error('     • Whitelist this server\'s IP in Atlas → Network Access.');
  console.error('     • Verify username/password, database name, and host reachability.');
  console.error('     • For a deliberate DB-less local run, set ALLOW_MOCK_DB=true.');
  console.error('════════════════════════════════════════════════════════════════════');
  console.error('');
  throw lastError || new Error('MongoDB connection failed');
};

/**
 * Check if using mock database
 */
export const isMockMode = (): boolean => useMock;

/**
 * Get User model (real or mock)
 */
export const getUserModel = () => {
  if (useMock) {
    return mockDB.users;
  }
  return mongoose.models.User;
};

/**
 * Get Company model (real or mock)
 */
export const getCompanyModel = () => {
  if (useMock) {
    return mockDB.companies;
  }
  return mongoose.models.Company;
};

/**
 * Disconnect from MongoDB
 */
export const disconnectDatabase = async (): Promise<void> => {
  if (!useMock) {
    await mongoose.disconnect();
  }
};

/**
 * Check if database is connected
 */
export const isConnected = (): boolean => {
  if (useMock) return true;
  return mongoose.connection.readyState === 1;
};

export { mockDB };
