import path from 'node:path';
import { fileURLToPath } from 'node:url';
import bcrypt from 'bcryptjs';
import cors from 'cors';
import express from 'express';
import helmet from 'helmet';
import type { NextFunction, Request, Response } from 'express';
import { z } from 'zod';
import { config } from './config';
import {
  findUserByIdentifier,
  initializeDatabase,
  pool,
} from './db';
import {
  createToken,
  requireAdmin,
  requireAuth,
  type AuthenticatedRequest,
} from './auth';
import { sharedJourneyRouter } from './sharedJourney';
import { journeyRequestDecisionRouter } from './journeyRequestDecisions';
import { familyJourneyRouter } from './familyJourney';
import { familyMeetingCompletionRouter } from './familyMeetingCompletion';
import { familyDecisionRouter } from './familyDecisions';
import { familyContactRouter } from './familyContact';
import { safetyRouter } from './safety';
import { profileCorrectionsRouter } from './profileCorrections';
import { familyCallRouter } from './familyCall';
import { cleanupExpiredGuardianContactData, guardianRouter } from './guardians';
import { guardianContactRouter } from './guardianContacts';
import { guardianQrRouter } from './guardianQr';
import { guardianAdminReviewsRouter } from './guardianAdminReviews';
import { guardianChangesRouter } from './guardianChanges';
import { representativeRouter } from './representatives';
import { adminCallsRouter } from './adminCalls';
import { adminUsersRouter } from './adminUsers';
import { liveKitWebhookRouter } from './liveKitWebhook';
import { startCallLifecycleMonitor } from './callLifecycle';
import { createRateLimit } from './rateLimit';
import { profilePhotosRouter } from './profilePhotos';
import { accessLogsRouter, recordUserAccess } from './accessLogs';
import { registrationRouter } from './registration';
import { accountDeletionRouter, purgeExpiredDeletedAccounts } from './accountDeletion';
import { relationshipWorkspaceRouter } from './relationshipWorkspace';
import { notificationsRouter } from './notifications';
import { platformPaymentsRouter } from './platformPayments';
import { identityVerificationRouter } from './identityVerification';
import { supportRouter } from './support';
import { adminActionNotificationsRouter } from './adminActionNotifications';
import { adminJourneyActionsRouter } from './adminJourneyActions';
import { journeyCertificatesRouter, purgeExpiredJourneyCertificates } from './journeyCertificates';
import { googleAuthRouter } from './googleAuth';

const app = express();
app.set('trust proxy', config.trustProxyHops);
const currentDirectory = path.dirname(fileURLToPath(import.meta.url));
const staticDirectory = path.resolve(currentDirectory, '..', 'dist');

const liveKitConnectSource = config.liveKit.url ?? '';
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      connectSrc: ["'self'", ...(liveKitConnectSource ? [liveKitConnectSource] : [])],
      imgSrc: ["'self'", 'data:', 'blob:'],
      mediaSrc: ["'self'", 'blob:'],
      workerSrc: ["'self'", 'blob:'],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
    },
  },
}));
app.use(
  cors({
    origin: config.corsOrigins.includes('*') ? true : config.corsOrigins,
    credentials: false,
  }),
);
app.use('/api', liveKitWebhookRouter);
app.use(express.json({ limit: '5mb' }));

const loginRateLimit = createRateLimit({
  windowMs: 15 * 60_000,
  maxRequests: 20,
  message: 'Too many login attempts. Try again later.',
});
const registrationRateLimit = createRateLimit({
  windowMs: 60 * 60_000,
  maxRequests: 30,
  message: 'Too many registration attempts. Try again later.',
});
app.use('/api/auth/login', loginRateLimit);
app.use('/api/v1/auth/register', registrationRateLimit);

app.get('/api/health', async (_request, response) => {
  await pool.query('SELECT 1');
  response.json({
    status: 'ok',
    database: 'postgresql',
    video: { provider: 'livekit', configured: config.liveKit.configured },
    authentication: { google: config.google.configured },
    timestamp: new Date().toISOString(),
  });
});

const loginSchema = z.object({
  identifier: z.string().trim().min(2).max(254).optional(),
  email: z.string().trim().min(2).max(254).optional(),
  password: z.string().min(8).max(128),
}).refine((value) => Boolean(value.identifier ?? value.email));

app.post('/api/auth/login', async (request, response) => {
  const parsed = loginSchema.safeParse(request.body);
  if (!parsed.success) {
    response.status(400).json({ error: 'Invalid login data' });
    return;
  }

  const user = await findUserByIdentifier(parsed.data.identifier ?? parsed.data.email!);
  if (!user || !user.is_active || !(await bcrypt.compare(parsed.data.password, user.password_hash))) {
    response.status(401).json({ error: 'Invalid email or password' });
    return;
  }

  const publicUser = {
    id: user.id,
    email: user.email,
    username: user.username,
    displayName: user.display_name,
    role: user.role,
    accountType: user.account_type,
  };
  await pool.query(
    'INSERT INTO audit_events (user_id, event_type, details) VALUES ($1, $2, $3::jsonb)',
    [user.id, 'login', '{}'],
  );
  await recordUserAccess(request, user.id);
  response.json({ token: createToken(publicUser, user.token_version), user: publicUser });
});

app.use('/api/v1', sharedJourneyRouter);
app.use('/api', googleAuthRouter);
app.use('/api/v1', registrationRouter);
app.use('/api/v1', journeyRequestDecisionRouter);
app.use('/api/v1', familyJourneyRouter);
app.use('/api/v1', familyMeetingCompletionRouter);
app.use('/api/v1', familyDecisionRouter);
app.use('/api/v1', familyContactRouter);
app.use('/api/v1', safetyRouter);
app.use('/api/v1', profileCorrectionsRouter);
app.use('/api/v1', familyCallRouter);
app.use('/api/v1', guardianRouter);
app.use('/api/v1', guardianContactRouter);
app.use('/api/v1', guardianQrRouter);
app.use('/api/v1', guardianAdminReviewsRouter);
app.use('/api/v1', guardianChangesRouter);
app.use('/api/v1', representativeRouter);
app.use('/api/v1', profilePhotosRouter);
app.use('/api/v1', accountDeletionRouter);
app.use('/api/v1', relationshipWorkspaceRouter);
app.use('/api/v1', notificationsRouter);
app.use('/api/v1', journeyCertificatesRouter);
app.use('/api', platformPaymentsRouter);
app.use('/api', identityVerificationRouter);
app.use('/api', supportRouter);
app.use('/api/admin', adminCallsRouter);
app.use('/api/admin', adminUsersRouter);
app.use('/api/admin', adminJourneyActionsRouter);
app.use('/api/admin', adminActionNotificationsRouter);
app.use('/api/admin', accessLogsRouter);

app.get('/api/auth/me', requireAuth, (request: AuthenticatedRequest, response) => {
  response.json({ user: request.authUser });
});

app.post('/api/auth/refresh', requireAuth, async (request: AuthenticatedRequest, response) => {
  const current = await pool.query<{ token_version: number }>(
    'SELECT token_version FROM users WHERE id=$1 AND is_active AND deleted_at IS NULL',
    [request.authUser!.id],
  );
  if (!current.rows[0]) { response.status(401).json({ error: 'Invalid or expired session' }); return; }
  response.json({
    token: createToken(request.authUser!, current.rows[0].token_version),
    user: request.authUser,
  });
});

app.post('/api/auth/logout', requireAuth, async (request: AuthenticatedRequest, response) => {
  await pool.query('UPDATE users SET token_version=token_version+1 WHERE id=$1', [request.authUser!.id]);
  response.json({ signedOut: true });
});

app.all('/api/state', requireAuth, (_request, response) => {
  response.status(410).json({ error: 'Legacy workflow state is retired. Update the application.' });
});

app.use('/api', (error: unknown, _request: Request, response: Response, _next: NextFunction) => {
  console.error('API request failed', error);
  response.status(500).json({ error: 'Server error' });
});

if (config.serveWebApp) {
  app.use(express.static(staticDirectory));
  app.get(/^(?!\/api).*/, (_request, response) => {
    response.sendFile(path.join(staticDirectory, 'index.html'));
  });
} else {
  app.get('/', (_request, response) => {
    response.json({ service: 'ala-talabak-api', status: 'ok' });
  });
}

const start = async (): Promise<number> => {
  await initializeDatabase();
  await cleanupExpiredGuardianContactData();
  await purgeExpiredDeletedAccounts();
  await purgeExpiredJourneyCertificates();
  startCallLifecycleMonitor();
  setInterval(() => {
    void cleanupExpiredGuardianContactData().catch((error: unknown) => {
      console.error('Guardian contact cleanup failed', error);
    });
  }, 15 * 60_000).unref();
  setInterval(() => {
    void purgeExpiredDeletedAccounts().catch((error: unknown) => {
      console.error('Deleted account cleanup failed', error);
    });
  }, 60 * 60_000).unref();
  setInterval(() => {
    void purgeExpiredJourneyCertificates().catch((error: unknown) => {
      console.error('Journey certificate cleanup failed', error);
    });
  }, 6 * 60 * 60_000).unref();
  app.listen(config.port, config.host, () => {
    console.log(`Ala-Talabak server listening on http://${config.host}:${config.port}`);
  });
  return config.port;
};

export const applicationReady = start();

applicationReady.catch((error: unknown) => {
  console.error('Server startup failed', error);
  process.exitCode = 1;
});

const shutdown = async (): Promise<void> => {
  await pool.end();
  process.exit(0);
};

process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
