import express from 'express';
import cors from 'cors';
import passport from 'passport';
import path from 'path';
import fs from 'fs';
import swaggerUI from 'swagger-ui-express';
import swaggerJsDoc from 'swagger-jsdoc';
import config from '@/shared/config';
import csrfOrigin from '@/middlewares/csrf-origin';
import {
  AUTH_COOKIE_NAME,
  AUTH_REFRESH_COOKIE_NAME,
} from '@/shared/constants/auth';
import ForbiddenError from '@/shared/errors/forbidden';
import {
  errorHandler,
  notFoundHandler,
} from '@/middlewares/error-handler';
import logger from '@/shared/logger';
import '@/auth/auth';

// Global error handlers to prevent server crashes from unhandled errors
process.on('uncaughtException', (error: Error) => {
  logger.error('Uncaught Exception - server continues running:', {
    message: error.message,
    stack: error.stack,
    name: error.name,
  });
});

process.on('unhandledRejection', (reason: unknown) => {
  const error = reason instanceof Error ? reason : new Error(String(reason));
  logger.error('Unhandled Promise Rejection - server continues running:', {
    message: error.message,
    stack: error.stack,
    name: error.name,
  });
});

import authRoutes from '@/routes/auth';
import fileRoutes from '@/routes/file';
import searchRoutes from '@/routes/search';
import publicCampusesRoutes from '@/routes/public_campuses';
import publicContentCatalogRoutes from '@/routes/public_content_catalog';
import contentCatalogRoutes from '@/routes/content_catalog';
import usersRoutes from '@/routes/users';
import rolesRoutes from '@/routes/roles';
import permissionsRoutes from '@/routes/permissions';
import organizationsRoutes from '@/routes/organizations';
import campusesRoutes from '@/routes/campuses';
import academicYearsRoutes from '@/routes/academic_years';
import gradesRoutes from '@/routes/grades';
import subjectsRoutes from '@/routes/subjects';
import staffRoutes from '@/routes/staff';
import classesRoutes from '@/routes/classes';
import classEnrollmentsRoutes from '@/routes/class_enrollments';
import classSubjectsRoutes from '@/routes/class_subjects';
import timetablesRoutes from '@/routes/timetables';
import timetablePeriodsRoutes from '@/routes/timetable_periods';
import attendanceSessionsRoutes from '@/routes/attendance_sessions';
import attendanceRecordsRoutes from '@/routes/attendance_records';
import assessmentsRoutes from '@/routes/assessments';
import assessmentResultsRoutes from '@/routes/assessment_results';
import messagesRoutes from '@/routes/messages';
import messageRecipientsRoutes from '@/routes/message_recipients';
import frameEntriesRoutes from '@/routes/frame_entries';
import userProgressRoutes from '@/routes/user_progress';
import safetyQuizResultsRoutes from '@/routes/safety_quiz_results';
import walkthroughCheckinsRoutes from '@/routes/walkthrough_checkins';
import communicationsRoutes from '@/routes/communications';
import personalityQuizResultsRoutes from '@/routes/personality_quiz_results';
import campusAttendanceRoutes from '@/routes/campus_attendance';
import staffAttendanceRoutes from '@/routes/staff_attendance';
import policyDocumentsRoutes from '@/routes/policy_documents';
import policyAcknowledgmentsRoutes from '@/routes/policy_acknowledgments';
import audioFilesRoutes from '@/routes/audio_files';

const app = express();

const authenticated = passport.authenticate('jwt', { session: false });

function getBaseUrl(url: string | undefined): string {
  if (!url) return '';
  return url.endsWith('/api') ? url.slice(0, -4) : url;
}

const swaggerOptions: swaggerJsDoc.Options = {
  definition: {
    openapi: '3.0.0',
    info: {
      version: '1.0.0',
      title: 'School Chain Manager API',
      description: [
        'REST API for the School Chain Manager backend.',
        '',
        '**Authentication is cookie-based.** Sign-in sets two HttpOnly cookies:',
        `a short-lived access cookie (\`${AUTH_COOKIE_NAME}\`, a signed JWT) and a`,
        `long-lived opaque refresh cookie (\`${AUTH_REFRESH_COOKIE_NAME}\`). The`,
        'browser never reads or sends tokens manually; they travel automatically',
        'as cookies. The Swagger "Authorize" cookie field is for tooling only.',
        '',
        '**Authorization is by permission.** Most routes require a',
        '`${METHOD}_${ENTITY}` CRUD permission (e.g. `READ_USERS`); product',
        'feature routes require a product-feature permission (e.g. `READ_FRAME`,',
        '`FILL_ATTENDANCE`, `TAKE_QUIZ`). `super_admin`/`system_admin` carry',
        'global access and bypass per-permission checks.',
        '',
        '**Generic-CRUD convention.** Entity routers (`users`, `roles`,',
        '`campuses`, …) expose the same shape: `GET /` (list → `ListResponse`),',
        '`GET /count`, `GET /autocomplete`, `GET /:id`, `POST /`,',
        '`POST /bulk-import`, `PUT /:id`, `DELETE /:id`, `POST /deleteByIds`.',
        'Errors share the `Error` schema.',
      ].join('\n'),
    },
    servers: [
      {
        url: getBaseUrl(process.env.NEXT_PUBLIC_BACK_API) || config.swaggerUrl,
        description: 'Development server',
      },
    ],
    components: {
      securitySchemes: {
        cookieAuth: {
          type: 'apiKey',
          in: 'cookie',
          name: AUTH_COOKIE_NAME,
          description:
            'HttpOnly access cookie set on sign-in/refresh. Sent automatically by the browser.',
        },
      },
      schemas: {
        Error: {
          type: 'object',
          description:
            'Standard error body produced by the terminal error handler.',
          properties: {
            message: { type: 'string' },
            code: { type: 'string', nullable: true },
            details: { nullable: true },
          },
          required: ['message'],
        },
        ListResponse: {
          type: 'object',
          description: 'Standard paginated list payload.',
          properties: {
            rows: { type: 'array', items: { type: 'object' } },
            count: { type: 'integer' },
          },
          required: ['rows', 'count'],
        },
        UserProfile: {
          type: 'object',
          description:
            'The authenticated user profile returned by sign-in, refresh, and `/me`.',
          properties: {
            id: { type: 'string', format: 'uuid' },
            email: { type: 'string' },
            name_prefix: { type: 'string', nullable: true },
            firstName: { type: 'string', nullable: true },
            lastName: { type: 'string', nullable: true },
            organizationId: { type: 'string', format: 'uuid', nullable: true },
            app_role: {
              type: 'object',
              nullable: true,
              properties: {
                id: { type: 'string', format: 'uuid' },
                name: { type: 'string' },
                scope: { type: 'string' },
                globalAccess: { type: 'boolean' },
              },
            },
            campusId: { type: 'string', format: 'uuid', nullable: true },
            permissions: { type: 'array', items: { type: 'string' } },
          },
        },
      },
      responses: {
        UnauthorizedError: {
          description: 'No valid session cookie (401).',
          content: {
            'application/json': {
              schema: { $ref: '#/components/schemas/Error' },
            },
          },
        },
        ForbiddenError: {
          description: 'Authenticated but lacks the required permission (403).',
          content: {
            'application/json': {
              schema: { $ref: '#/components/schemas/Error' },
            },
          },
        },
        ValidationError: {
          description: 'Invalid input or not found (400).',
          content: {
            'application/json': {
              schema: { $ref: '#/components/schemas/Error' },
            },
          },
        },
      },
    },
    security: [{ cookieAuth: [] }],
  },
  apis: ['./src/routes/*.ts'],
};

const specs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(specs));

app.use(
  cors({
    credentials: true,
    origin(origin, callback) {
      if (
        !origin ||
        config.auth.allowAllOrigins ||
        config.auth.allowedOrigins.includes(origin)
      ) {
        callback(null, origin || true);
        return;
      }
      callback(new ForbiddenError());
    },
  }),
);

app.use(express.json());
app.use('/api', csrfOrigin);

app.use('/api/auth', authRoutes);
app.use('/api/file', fileRoutes);
app.use('/api/public/campuses', publicCampusesRoutes);
app.use('/api/public/content-catalog', publicContentCatalogRoutes);
app.enable('trust proxy');

app.use('/api/users', authenticated, usersRoutes);
app.use('/api/roles', authenticated, rolesRoutes);
app.use('/api/permissions', authenticated, permissionsRoutes);
app.use('/api/organizations', authenticated, organizationsRoutes);
app.use('/api/campuses', authenticated, campusesRoutes);
app.use('/api/academic_years', authenticated, academicYearsRoutes);
app.use('/api/grades', authenticated, gradesRoutes);
app.use('/api/subjects', authenticated, subjectsRoutes);
app.use('/api/staff', authenticated, staffRoutes);
app.use('/api/classes', authenticated, classesRoutes);
app.use('/api/class_enrollments', authenticated, classEnrollmentsRoutes);
app.use('/api/class_subjects', authenticated, classSubjectsRoutes);
app.use('/api/timetables', authenticated, timetablesRoutes);
app.use('/api/timetable_periods', authenticated, timetablePeriodsRoutes);
app.use('/api/attendance_sessions', authenticated, attendanceSessionsRoutes);
app.use('/api/attendance_records', authenticated, attendanceRecordsRoutes);
app.use('/api/assessments', authenticated, assessmentsRoutes);
app.use('/api/assessment_results', authenticated, assessmentResultsRoutes);
app.use('/api/messages', authenticated, messagesRoutes);
app.use('/api/message_recipients', authenticated, messageRecipientsRoutes);
app.use('/api/frame_entries', authenticated, frameEntriesRoutes);
app.use('/api/user_progress', authenticated, userProgressRoutes);
app.use('/api/safety_quiz_results', authenticated, safetyQuizResultsRoutes);
app.use('/api/walkthrough_checkins', authenticated, walkthroughCheckinsRoutes);
app.use('/api/communications', authenticated, communicationsRoutes);
app.use(
  '/api/personality_quiz_results',
  authenticated,
  personalityQuizResultsRoutes,
);
app.use('/api/campus_attendance', authenticated, campusAttendanceRoutes);
app.use('/api/staff_attendance', authenticated, staffAttendanceRoutes);
app.use('/api/content-catalog', authenticated, contentCatalogRoutes);
app.use('/api/policy_documents', authenticated, policyDocumentsRoutes);
app.use('/api/policy_acknowledgments', authenticated, policyAcknowledgmentsRoutes);
app.use('/api/audio_files', authenticated, audioFilesRoutes);
app.use('/api/search', authenticated, searchRoutes);

// Unmatched API routes → centralized 404 (the SPA fallback below handles the rest).
app.use('/api', notFoundHandler);

const __dirname = import.meta.dirname;
const publicDir = path.join(__dirname, '../public');

if (fs.existsSync(publicDir)) {
  app.use('/', express.static(publicDir));

  app.get('/*splat', (_request, response) => {
    response.sendFile(path.resolve(publicDir, 'index.html'));
  });
}

// Terminal error middleware — must be registered after all routes/middleware.
app.use(errorHandler);

const PORT = config.serverPort;

app.listen(PORT, () => {
  logger.info(`Listening on port ${PORT}`);
});

export default app;
