// Required Packages:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const express = require('express');
const cors = require('cors');
const swagger = require('swagger-ui-express');

// Imports:
import { ChainEndpoint } from 'cookietrack-types';
// import { fetchPrices, testFetchPrices } from './price-fetcher';
import type { Request, Response, Application } from 'express';
import type { Chain } from 'cookietrack-types';

// Fetching Required JSON Files:
const routes: Record<string, string[]> = require('../static/routes.json');
const swaggerDocs: JSON = require('../static/swagger.json');

// Fetching Firebase Logger Compatibility Patch:
require("firebase-functions/lib/logger/compat");

// Initializing Firebase App:
admin.initializeApp();

// Initializing Express Server:
const app: Application = express();
app.use(cors());

// Initializations:
const repository: string = 'https://github.com/CookieTrack-io/cookietrack-api';
const discord: string = 'https://discord.com/invite/DzADcq7y75';
const rootResponse: string = `<title>CookieTrack API</title><p>Click <a href="${repository}">here</a> to see the API's repository, or <a href="/docs">here</a> to see its OpenAPI documentation.</p>`;
const errorResponse: string = `<p>Invalid route.</p>`;
const rateLimitedResponse: string = `<p>Rate limit reached. Contact us through <a href="${discord}">Discord</a> if you believe this is not working as intended.</p>`;
const filter: RegExp = /[^a-zA-Z0-9]/;

// Settings:
const localTesting = false;
const emulatorTesting = false;
const rateLimited = true;
const localTestingPort = 3000;
const maxQueries = 200;
const rateLimitTimer = 21600000;

/* ========================================================================================================================================================================= */

// Default Endpoint:
app.get('/', (req: Request, res: Response) => {
  res.send(rootResponse);
});

// Swagger Documentation Endpoint:
app.use('/docs', swagger.serve, swagger.setup(swaggerDocs));

// Routes Endpoint:
app.get('/routes', (req: Request, res: Response) => {
  res.end(JSON.stringify(routes, null, ' '));
});

/* ========================================================================================================================================================================= */

// Chain Endpoints:
(Object.keys(ChainEndpoint) as Chain[]).forEach(chain => {
  app.get(`/${ChainEndpoint[chain]}/*`, async (req: Request, res: Response) => {
    let rateLimitExceeded = false;
    if(!localTesting && rateLimited) {
      let rateLimits = admin.firestore().collection('rateLimits');
      let userID = 'u_' + (req.headers['x-forwarded-for'] as String).split(',')[0];
      let userDoc = rateLimits.doc(userID);
      let doc = await userDoc.get();
      if(doc.exists) {
        if(doc.data().lastTimestamp.toMillis() < (Date.now() - rateLimitTimer)) {
          let usageHistory = { timestamp: doc.data().lastTimestamp, queries: doc.data().queries };
          await userDoc.update({ usage: admin.firestore.FieldValue.arrayUnion(usageHistory), lastTimestamp: admin.firestore.FieldValue.serverTimestamp(), queries: 1 });
        } else if(doc.data().queries >= maxQueries) {
          rateLimitExceeded = true;
        } else {
          await userDoc.update({ queries: admin.firestore.FieldValue.increment(1) });
        }
      } else {
        await userDoc.set({ lastTimestamp: admin.firestore.FieldValue.serverTimestamp(), queries: 1, usage: [] });
      }
    }
    if(!rateLimitExceeded) {
      try {
        let query = req.originalUrl.split('/');
        let chain = query[1];
        let input = query[2].split('?')[0];
        if(input.match(filter)) {
          console.error(`Invalid Query (/${chain}/${input})`);
          res.send(errorResponse);
        } else if(!routes[chain].includes(input)) {
          console.error(`Unavailable Route (/${chain}/${input})`);
          res.send(errorResponse);
        } else {
          console.info(`Loading: ${req.originalUrl}`);
          res.end(await require(`../dist/routes/${chain}/${input}.js`).get(req));
        }
      } catch(err: any) {
        console.error(err);
        res.send(errorResponse);
      }
    } else {
      res.send(rateLimitedResponse);
    }
  });
});

/* ========================================================================================================================================================================= */

// 404 Response:
app.all('*', async (req: Request, res: Response) => {
  res.send(errorResponse);
});

/* ========================================================================================================================================================================= */

// Local Testing:
if(localTesting) {
  app.listen(localTestingPort, () => { console.info(`API Up on http://127.0.0.1:${localTestingPort}`); });

// Emulator Testing:
} else if(emulatorTesting) {
  exports.app = functions.https.onRequest(app);
  // exports.priceFetcher = functions.https.onRequest(async (req: Request, res: Response) => { res.end(testFetchPrices()); });

// Exporting Firebase Functions:
} else {
  exports.app = functions.runWith({ memory: '1GB', timeoutSeconds: 120 }).https.onRequest(app);
  // exports.priceFetcher = functions.pubsub.schedule('every 15 minutes').onRun(() => { fetchPrices(); return null; });
}