import type { NextFunction, Request, Response } from 'express';
import { Router } from 'express';
import { z } from 'zod';

import { ensureMinioBucket, minioBucket, minioClient, minioPresignedExpirySeconds } from '../utils/minio';

const adminUsername = process.env.ADMIN_USERNAME;
const adminPassword = process.env.ADMIN_PASSWORD;

if (!adminUsername || !adminPassword) {
  throw new Error('ADMIN_USERNAME and ADMIN_PASSWORD must be set to use the admin dashboard');
}

const router = Router();

const uploadSchema = z.object({
  fileName: z.string().trim().min(1).max(255),
  prefix: z.string().trim().optional(),
});

const objectKeySchema = z.object({
  objectKey: z.string().trim().min(1),
});

const listSchema = z.object({
  prefix: z.string().trim().optional(),
  limit: z.coerce.number().int().min(1).max(100).default(20),
});

const sanitizeSegment = (value: string): string => value.replace(/[^a-zA-Z0-9._/-]/g, '_');

const buildObjectKey = (fileName: string, prefix?: string): string => {
  const safePrefix = prefix ? sanitizeSegment(prefix).replace(/^\/+|\/+$/g, '') : 'admin-tests';
  const safeFileName = sanitizeSegment(fileName);

  return `${safePrefix}/admin/${Date.now()}-${safeFileName}`;
};

const sendAuthRequired = (res: Response, message: string): Response => {
  res.setHeader('WWW-Authenticate', 'Basic realm="Video Admin Dashboard"');
  return res.status(401).json({ message });
};

const requireAdminAuth = (req: Request, res: Response, next: NextFunction): void => {
  const authorization = req.headers.authorization;

  if (!authorization?.startsWith('Basic ')) {
    sendAuthRequired(res, 'Authorization required for admin dashboard');
    return;
  }

  const rawCredentials = authorization.slice(6);
  let decoded: string;

  try {
    decoded = Buffer.from(rawCredentials, 'base64').toString('utf8');
  } catch {
    sendAuthRequired(res, 'Malformed authorization header');
    return;
  }

  const separatorIndex = decoded.indexOf(':');

  if (separatorIndex < 0) {
    sendAuthRequired(res, 'Malformed authorization header');
    return;
  }

  const username = decoded.slice(0, separatorIndex);
  const password = decoded.slice(separatorIndex + 1);

  if (username !== adminUsername || password !== adminPassword) {
    sendAuthRequired(res, 'Invalid admin credentials');
    return;
  }

  next();
};

router.use(requireAdminAuth);

router.get('/', (_req, res) => {
  const html = `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Video Admin Dashboard</title>
    <style>
      body {
        font-family: system-ui, sans-serif;
        margin: 2rem;
        color: #0c0d0f;
        background: #f8fafc;
      }

      main {
        max-width: 960px;
        margin: auto;
      }

      form > * {
        display: block;
        margin: 0.5rem 0;
      }

      .pill-button {
        border: none;
        padding: 0.6rem 1.2rem;
        border-radius: 999px;
        background: #2f80ed;
        color: white;
        font-weight: 600;
        cursor: pointer;
      }

      .status {
        margin-top: 1rem;
        min-height: 1.5rem;
        padding: 0.5rem 0.75rem;
        background: white;
        border-radius: 0.375rem;
        box-shadow: 0 2px 8px rgba(15, 23, 42, 0.1);
      }

      .object-list {
        list-style: none;
        padding: 0;
        margin: 1rem 0;
      }

      .object-list li {
        background: white;
        margin-bottom: 0.5rem;
        padding: 0.75rem;
        border-radius: 0.375rem;
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 1rem;
        box-shadow: 0 1px 4px rgba(15, 23, 42, 0.08);
      }

      .monospace {
        font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
        word-break: break-all;
      }

      @media (max-width: 600px) {
        .object-list li {
          flex-direction: column;
          align-items: flex-start;
        }
      }
    </style>
  </head>
  <body>
    <main>
      <h1>Video Admin Dashboard</h1>
      <p>Use this interface to request a presigned upload URL, push a file into MinIO, and inspect the objects stored in <strong>${minioBucket}</strong>.</p>
      <section>
        <h2>Upload a file</h2>
        <form id="upload-form">
          <label>
            Pick a file
            <input type="file" id="file-input" required />
          </label>
          <label>
            Prefix
            <input type="text" id="prefix-input" placeholder="Optional folder prefix" />
          </label>
          <button type="submit" class="pill-button">Upload via presigned URL</button>
        </form>
        <div class="status" id="status">Ready</div>
      </section>
      <section>
        <h2>Bucket contents</h2>
        <button id="refresh" class="pill-button">Refresh list</button>
        <ul class="object-list" id="object-list"></ul>
      </section>
    </main>
    <script>
      const statusEl = document.getElementById('status');
      const objectList = document.getElementById('object-list');
      const refreshButton = document.getElementById('refresh');

      const formatSize = (bytes) => {
        if (!bytes && bytes !== 0) return 'N/A';
        const kb = bytes / 1024;
        if (kb < 1) return bytes + ' B';
        const mb = kb / 1024;
        if (mb < 1) return kb.toFixed(2) + ' KB';
        return mb.toFixed(2) + ' MB';
      };

      const fetchObjects = async () => {
        try {
          statusEl.textContent = 'Fetching objects…';
          const response = await fetch('/admin/objects', { credentials: 'include' });
          const payload = await response.json();

          if (!response.ok) {
            throw new Error(payload.message || response.statusText);
          }

          statusEl.textContent = 'Found ' + payload.objects.length + ' object(s)';
          objectList.innerHTML = payload.objects
          .map(
              (item) => (
                '<li>' +
                '<div>' +
                '<div class="monospace">' +
                item.objectKey +
                '</div>' +
                '<small>' +
                formatSize(item.size) +
                '</small>' +
                '</div>' +
                '<button data-key="' +
                item.objectKey +
                '" class="pill-button" style="background:#f97316">Delete</button>' +
                '</li>'
              ),
            )
            .join('');
        } catch (error) {
          statusEl.textContent = 'Unable to load objects: ' + error.message;
        }
      };

      objectList.addEventListener('click', async (event) => {
        const button = event.target.closest('button');
        if (!button || !button.dataset.key) return;

        if (!confirm('Delete ' + button.dataset.key + '?')) return;

        try {
          statusEl.textContent = 'Deleting object…';
          const response = await fetch('/admin/object?objectKey=' + encodeURIComponent(button.dataset.key), {
            method: 'DELETE',
            credentials: 'include',
          });
          const payload = await response.json();

          if (!response.ok) {
            throw new Error(payload.message || response.statusText);
          }

          statusEl.textContent = payload.message;
          fetchObjects();
        } catch (error) {
          statusEl.textContent = 'Delete failed: ' + error.message;
        }
      });

      document.getElementById('upload-form').addEventListener('submit', async (event) => {
        event.preventDefault();

        const fileInput = document.getElementById('file-input');
        const prefixInput = document.getElementById('prefix-input');
        const file = fileInput.files?.[0];

        if (!file) {
          statusEl.textContent = 'Choose a file before uploading.';
          return;
        }

        try {
          statusEl.textContent = 'Requesting upload URL…';
          const uploadMetaResponse = await fetch('/admin/upload-url', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            credentials: 'include',
            body: JSON.stringify({
              fileName: file.name,
              prefix: prefixInput.value.trim() || undefined,
            }),
          });

          const uploadMeta = await uploadMetaResponse.json();

          if (!uploadMetaResponse.ok) {
            throw new Error(uploadMeta.message || uploadMetaResponse.statusText);
          }

          statusEl.textContent = 'Uploading to MinIO…';

          const putResponse = await fetch(uploadMeta.uploadUrl, {
            method: 'PUT',
            body: file,
            headers: {
              'Content-Type': file.type || 'application/octet-stream',
            },
          });

          if (!putResponse.ok) {
            throw new Error('MinIO rejected the file upload');
          }

          statusEl.textContent = 'Upload complete · ' + uploadMeta.objectKey;
          fetchObjects();
        } catch (error) {
          statusEl.textContent = 'Upload failed: ' + error.message;
        }
      });

      refreshButton.addEventListener('click', fetchObjects);
      fetchObjects();
    </script>
  </body>
</html>`;

  res.setHeader('Content-Type', 'text/html; charset=utf-8');
  res.send(html);
});

router.post('/upload-url', async (req, res) => {
  const parsed = uploadSchema.safeParse(req.body);

  if (!parsed.success) {
    res.status(400).json({ message: 'Invalid request body', errors: parsed.error.flatten() });
    return;
  }

  await ensureMinioBucket();

  const objectKey = buildObjectKey(parsed.data.fileName, parsed.data.prefix);
  const uploadUrl = await minioClient.presignedPutObject(minioBucket, objectKey, minioPresignedExpirySeconds);
  const expiresAt = new Date(Date.now() + minioPresignedExpirySeconds * 1000);

  res.status(201).json({
    message: 'Upload URL generated',
    bucket: minioBucket,
    objectKey,
    uploadUrl,
    expiresAt,
    expiresInSeconds: minioPresignedExpirySeconds,
  });
});

router.get('/objects', async (req, res) => {
  const parsed = listSchema.safeParse(req.query);

  if (!parsed.success) {
    res.status(400).json({ message: 'Invalid query params', errors: parsed.error.flatten() });
    return;
  }

  await ensureMinioBucket();

  const objects = await new Promise<
    { objectKey: string | undefined; size: number; etag: string | undefined; lastModified: Date | undefined }[]
  >((resolve, reject) => {
    const results: {
      objectKey: string | undefined;
      size: number;
      etag: string | undefined;
      lastModified: Date | undefined;
    }[] = [];
    const stream = minioClient.listObjectsV2(minioBucket, parsed.data.prefix, true);
    let finished = false;

    const finish = () => {
      if (finished) return;
      finished = true;
      resolve(results);
    };

    stream.on('data', (item) => {
      if (results.length >= parsed.data.limit) {
        stream.destroy();
        return;
      }

      results.push({
        objectKey: item.name,
        size: item.size,
        etag: item.etag,
        lastModified: item.lastModified,
      });
    });

    stream.on('error', (error) => {
      if (!finished) {
        finished = true;
        reject(error);
      }
    });

    stream.on('end', finish);
    stream.on('close', finish);
  });

  res.json({ bucket: minioBucket, count: objects.length, objects });
});

router.delete('/object', async (req, res) => {
  const parsed = objectKeySchema.safeParse(req.query);

  if (!parsed.success) {
    res.status(400).json({ message: 'Invalid query params', errors: parsed.error.flatten() });
    return;
  }

  await ensureMinioBucket();
  await minioClient.removeObject(minioBucket, parsed.data.objectKey);

  res.json({ message: 'Object deleted', bucket: minioBucket, objectKey: parsed.data.objectKey });
});

export default router;
