import cors from 'cors';
import express from 'express';
import ejs from 'ejs';

import serveDirectoryMetadata, { getMetadataMoreEasier } from './actions/serveDirectoryMetadata.js';
import serveFileMetadata from './actions/serveFileMetadata.js';
import serveFile from './actions/serveFile.js';
import serveModule from './actions/serveModule.js';

import allowQuery from './middleware/allowQuery.js';
import findEntry from './middleware/findEntry.js';
import noQuery from './middleware/noQuery.js';
import redirectLegacyURLs from './middleware/redirectLegacyURLs.js';
import requestLog from './middleware/requestLog.js';
import validateFilename from './middleware/validateFilename.js';
import validatePackagePathname from './middleware/validatePackagePathname.js';
import validatePackageName from './middleware/validatePackageName.js';
import validatePackageVersion from './middleware/validatePackageVersion.js';

const renderFolderBrowser = async (req, res, next) => {

  res.set({
    'Cache-Control': 'public, max-age=14400', // 4 hours
    'Cache-Tag': 'browse'
  });

  const html = await ejs.render(`<%if(path!=="/"){path+="/"}%>
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Index of <%= path %></title>
    <style>
        body {
            font-size: 16px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            line-height: 1.5;
            padding: 0 10px 5px
        }

        table {
            width: 100%;
            border-collapse: collapse;
            font: .85em Monaco, monospace
        }

        tr.even {
            background-color: #eee
        }

        th {
            text-align: left
        }

        td,
        th {
            padding: .1em .25em
        }

        .version-wrapper {
            line-height: 2.25em;
            float: right
        }

        #version {
            font-size: 1em
        }

        address {
            text-align: right
        }
    </style>
</head>

<body>
    <h1>Index of <%= path %></h1>
    <hr />
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Type</th>
                <th>Size</th>
                <th>Last Modified</th>
            </tr>
        </thead>
        <tbody>
            <% 
                files.sort((fileA,fileB) => { 
                    let nameA = fileA.path.toUpperCase();
                    let nameB = fileB.path.toUpperCase();
                    if(fileA.type === "directory") return -1;
                    else if(fileB.type === "directory") return 1;
                    else if(nameA<nameB) return -1;
                    else if(nameA>nameB) return 1;
                    else return 0;
                })
                if(path!=="/"){
                    files.splice(0,0,{
                            path: "..",
                            type: "directory",
                    });
                }

            %>
            <% files.forEach((file,index) => { let name = file.path.replace(path,"")+(file.type==="directory"?"/":""); %>
                <tr class="<%= index%2===0?"even":"odd" %> ">
                    <td><a title="<%= name %>" href="<%= name %>"><%= name %></a></td>
                    <td><%= file.type==="directory"?"-":file.contentType %></td>
                    <td><%= file.type==="directory"?"-":(()=>{
                        if(file.size<=1024) return file.size+" B";
                        else if(file.size<=1024*1024) return (file.size/1024).toFixed(2)+" KiB";
                        else return (file.size/1024/1024).toFixed(2)+" MB";
                    })() %></td>
                    <td><%= file.type==="directory"?"-":new Date(Date.parse(file.lastModified)).toISOString()
                        %></td>
                </tr>
            <% }) %>
        </tbody>
    </table>
    <hr />
    <address><%= package %> </address>
    <address>186526 Edge/1.20.1</address>
</body>

</html>`, Object.assign({ package: `${req.packageName}@${req.packageVersion}` }, await getMetadataMoreEasier(req, res)), { async: true });
  res.send(html);
};

function createApp(callback) {
  const app = express();
  callback(app);
  return app;
}

export default function createServer() {
  return createApp(app => {
    app.disable('x-powered-by');
    app.enable('trust proxy');
    app.enable('strict routing');

    app.use(cors());
    app.use(express.static('public', { maxAge: '1y' }));

    app.use(requestLog);

    app.use(redirectLegacyURLs);

    app.all('/', (req, res) => {
      res.send("To infinity and beyond!");
    })

    app.use(
      '/browse',
      createApp(app => {
        app.enable('strict routing');

        app.get(
          '*/',
          noQuery(),
          validatePackagePathname,
          validatePackageName,
          validatePackageVersion,
          renderFolderBrowser,
        );
        app.get(
          '*',
          (req, res, next) => {
            res.redirect(req.path.replace("/browser", ""));
          }
        );
      })
    );

    // We need to route in this weird way because Express
    // doesn't have a way to route based on query params.
    const metadataApp = createApp(app => {
      app.enable('strict routing');

      app.get(
        '*/',
        allowQuery('meta'),
        validatePackagePathname,
        validatePackageName,
        validatePackageVersion,
        validateFilename,
        serveDirectoryMetadata
      );

      app.get(
        '*',
        allowQuery('meta'),
        validatePackagePathname,
        validatePackageName,
        validatePackageVersion,
        validateFilename,
        serveFileMetadata
      );
    });

    app.use((req, res, next) => {
      if (req.query.meta != null) {
        metadataApp(req, res);
      } else {
        next();
      }
    });

    // We need to route in this weird way because Express
    // doesn't have a way to route based on query params.
    const moduleApp = createApp(app => {
      app.enable('strict routing');

      app.get(
        '*',
        allowQuery('module'),
        validatePackagePathname,
        validatePackageName,
        validatePackageVersion,
        validateFilename,
        findEntry,
        serveModule
      );
    });

    app.use((req, res, next) => {
      if (req.query.module != null) {
        moduleApp(req, res);
      } else {
        next();
      }
    });

    // Send old */ requests to the new /browse UI.
    app.get('*/', noQuery(),
      validatePackagePathname,
      validatePackageName,
      validatePackageVersion,
      renderFolderBrowser
    );

    app.get(
      '*',
      noQuery(),
      validatePackagePathname,
      validatePackageName,
      validatePackageVersion,
      validateFilename,
      findEntry,
      serveFile
    );
  });
}
