import express from "express";
import { readFileSync } from "node:fs";
import {
  CLOUDWATCH_DEFAULT_LIMIT,
  CLOUDWATCH_DEFAULT_WINDOW_MS,
  CLOUDWATCH_ENDPOINT,
  CLOUDWATCH_REGION,
  DEFAULT_REFRESH_MS,
  PORT,
  S3_ENDPOINT,
  S3_REGION,
  SES_ENDPOINT,
  SECRETS_ENDPOINT,
  SECRETS_REGION
} from "./server/config.js";
import { getClientConfig, renderHtml } from "./server/page.js";
import {
  buildAttachmentDisposition,
  buildInlineDisposition,
  clampNumber,
  findSesMessageById,
  loadLogEvents,
  loadLogGroups,
  loadLogStreams,
  loadMessageAttachment,
  loadMessages,
  loadS3Buckets,
  loadS3ObjectDownload,
  loadS3ObjectPreview,
  loadS3Objects,
  loadSecretValue,
  loadSecrets,
  loadServiceHealthSummary
} from "./server/localstack-service.js";

const app = express();
const CLIENT_APP_PATH = new URL("./public/client-app.js", import.meta.url);
const CLIENT_APP_SOURCE = readFileSync(CLIENT_APP_PATH, "utf8");

app.use((req, res, next) => {
  res.set("Cache-Control", "no-store");
  next();
});

app.get("/", (req, res) => {
  res.type("html").send(renderHtml());
});

app.get("/app.js", (req, res) => {
  res.type("application/javascript").send(`${CLIENT_APP_SOURCE}\n\nclientApp(${JSON.stringify(getClientConfig())});\n`);
});

app.get("/health", (req, res) => {
  res.json({
    ok: true,
    endpoint: SES_ENDPOINT,
    endpoints: {
      ses: SES_ENDPOINT,
      cloudWatchLogs: CLOUDWATCH_ENDPOINT,
      secretsManager: SECRETS_ENDPOINT,
      s3: S3_ENDPOINT
    },
    port: PORT,
    defaultRefreshMs: DEFAULT_REFRESH_MS
  });
});

app.get("/api/service-health", async (req, res) => {
  try {
    res.json(await loadServiceHealthSummary());
  } catch (error) {
    console.error("Error fetching service health:", error);
    res.status(502).json({
      error: "Unable to fetch LocalStack service health",
      details: error.message
    });
  }
});

app.get("/api/messages", async (req, res) => {
  try {
    res.json(await loadMessages());
  } catch (error) {
    console.error("Error fetching messages:", error);
    res.status(502).json({
      error: "Unable to fetch messages from LocalStack SES",
      details: error.message,
      endpoint: SES_ENDPOINT
    });
  }
});

app.get("/api/messages/:id/raw", async (req, res) => {
  try {
    const message = await findSesMessageById(req.params.id);

    if (!message) {
      res.status(404).type("text/plain").send("Message not found");
      return;
    }

    res.type("text/plain").send(message.RawData || "");
  } catch (error) {
    console.error("Error fetching raw message:", error);
    res.status(502).type("text/plain").send(`Unable to fetch raw message: ${error.message}`);
  }
});

app.get("/api/messages/:id/attachments/:index", async (req, res) => {
  try {
    const attachmentIndex = Number.parseInt(req.params.index, 10);

    if (!Number.isInteger(attachmentIndex) || attachmentIndex < 0) {
      res.status(400).type("text/plain").send("Attachment index must be a non-negative integer");
      return;
    }

    const attachment = await loadMessageAttachment(req.params.id, attachmentIndex);

    if (!attachment) {
      res.status(404).type("text/plain").send("Attachment not found");
      return;
    }

    res.setHeader("Content-Type", attachment.contentType);
    res.setHeader("Content-Disposition", buildAttachmentDisposition(attachment.filename));
    res.setHeader("Content-Length", String(attachment.content.length));
    res.send(attachment.content);
  } catch (error) {
    console.error("Error downloading attachment:", error);
    res.status(502).type("text/plain").send(`Unable to download attachment: ${error.message}`);
  }
});

app.get("/api/logs/groups", async (req, res) => {
  try {
    const groups = await loadLogGroups();
    res.json({
      endpoint: CLOUDWATCH_ENDPOINT,
      region: CLOUDWATCH_REGION,
      groups
    });
  } catch (error) {
    console.error("Error fetching log groups:", error);
    res.status(502).json({
      error: "Unable to fetch CloudWatch log groups from LocalStack",
      details: error.message,
      endpoint: CLOUDWATCH_ENDPOINT
    });
  }
});

app.get("/api/logs/streams", async (req, res) => {
  try {
    const logGroupName = String(req.query.group || "");

    if (!logGroupName) {
      res.status(400).json({ error: "Query parameter 'group' is required" });
      return;
    }

    res.json({
      logGroupName,
      streams: await loadLogStreams(logGroupName)
    });
  } catch (error) {
    console.error("Error fetching log streams:", error);
    res.status(502).json({
      error: "Unable to fetch CloudWatch log streams from LocalStack",
      details: error.message,
      endpoint: CLOUDWATCH_ENDPOINT
    });
  }
});

app.get("/api/logs/events", async (req, res) => {
  try {
    const logGroupName = String(req.query.group || "");
    const logStreamName = String(req.query.stream || "");
    const windowMs = clampNumber(req.query.windowMs, CLOUDWATCH_DEFAULT_WINDOW_MS, 60 * 1000, 24 * 60 * 60 * 1000);
    const limit = clampNumber(req.query.limit, CLOUDWATCH_DEFAULT_LIMIT, 25, 500);

    if (!logGroupName) {
      res.status(400).json({ error: "Query parameter 'group' is required" });
      return;
    }

    res.json(await loadLogEvents({ logGroupName, logStreamName, windowMs, limit }));
  } catch (error) {
    console.error("Error fetching log events:", error);
    res.status(502).json({
      error: "Unable to fetch CloudWatch log events from LocalStack",
      details: error.message,
      endpoint: CLOUDWATCH_ENDPOINT
    });
  }
});

app.get("/api/secrets", async (req, res) => {
  try {
    res.json(await loadSecrets());
  } catch (error) {
    console.error("Error fetching secrets:", error);
    res.status(502).json({
      error: "Unable to fetch Secrets Manager secrets from LocalStack",
      details: error.message,
      endpoint: SECRETS_ENDPOINT
    });
  }
});

app.get("/api/secrets/value", async (req, res) => {
  try {
    const secretId = String(req.query.id || "");

    if (!secretId) {
      res.status(400).json({ error: "Query parameter 'id' is required" });
      return;
    }

    res.json(await loadSecretValue(secretId));
  } catch (error) {
    if (error?.name === "ResourceNotFoundException") {
      res.status(404).json({
        error: "Secret not found",
        details: error.message,
        endpoint: SECRETS_ENDPOINT
      });
      return;
    }

    console.error("Error fetching secret value:", error);
    res.status(502).json({
      error: "Unable to fetch Secrets Manager value from LocalStack",
      details: error.message,
      endpoint: SECRETS_ENDPOINT
    });
  }
});

app.get("/api/s3/buckets", async (req, res) => {
  try {
    res.json(await loadS3Buckets());
  } catch (error) {
    console.error("Error fetching S3 buckets:", error);
    res.status(502).json({
      error: "Unable to fetch S3 buckets from LocalStack",
      details: error.message,
      endpoint: S3_ENDPOINT
    });
  }
});

app.get("/api/s3/objects", async (req, res) => {
  try {
    const bucket = String(req.query.bucket || "");
    const prefix = String(req.query.prefix || "");

    if (!bucket) {
      res.status(400).json({ error: "Query parameter 'bucket' is required" });
      return;
    }

    res.json(await loadS3Objects({ bucket, prefix }));
  } catch (error) {
    console.error("Error fetching S3 objects:", error);
    res.status(502).json({
      error: "Unable to fetch S3 objects from LocalStack",
      details: error.message,
      endpoint: S3_ENDPOINT
    });
  }
});

app.get("/api/s3/object", async (req, res) => {
  try {
    const bucket = String(req.query.bucket || "");
    const key = String(req.query.key || "");

    if (!bucket || !key) {
      res.status(400).json({ error: "Query parameters 'bucket' and 'key' are required" });
      return;
    }

    res.json(await loadS3ObjectPreview({ bucket, key }));
  } catch (error) {
    if (error?.name === "NoSuchKey" || error?.name === "NotFound") {
      res.status(404).json({
        error: "Object not found",
        details: error.message,
        endpoint: S3_ENDPOINT
      });
      return;
    }

    console.error("Error fetching S3 object preview:", error);
    res.status(502).json({
      error: "Unable to fetch S3 object preview from LocalStack",
      details: error.message,
      endpoint: S3_ENDPOINT
    });
  }
});

app.get("/api/s3/download", async (req, res) => {
  try {
    const bucket = String(req.query.bucket || "");
    const key = String(req.query.key || "");
    const inline = String(req.query.inline || "") === "1";

    if (!bucket || !key) {
      res.status(400).type("text/plain").send("Query parameters 'bucket' and 'key' are required");
      return;
    }

    const object = await loadS3ObjectDownload({ bucket, key });

    res.setHeader("Content-Type", object.contentType);
    res.setHeader(
      "Content-Disposition",
      inline ? buildInlineDisposition(object.filename) : buildAttachmentDisposition(object.filename)
    );
    res.setHeader("Content-Length", String(object.content.length));
    res.send(object.content);
  } catch (error) {
    if (error?.name === "NoSuchKey" || error?.name === "NotFound") {
      res.status(404).type("text/plain").send("Object not found");
      return;
    }

    console.error("Error downloading S3 object:", error);
    res.status(502).type("text/plain").send(`Unable to download S3 object: ${error.message}`);
  }
});

app.listen(PORT, () => {
  console.log(`LocalStack inspector is running on http://localhost:${PORT}`);
  console.log(`Watching LocalStack SES endpoint at ${SES_ENDPOINT}`);
  console.log(`Watching LocalStack CloudWatch Logs endpoint at ${CLOUDWATCH_ENDPOINT} (${CLOUDWATCH_REGION})`);
  console.log(`Watching LocalStack Secrets Manager endpoint at ${SECRETS_ENDPOINT} (${SECRETS_REGION})`);
  console.log(`Watching LocalStack S3 endpoint at ${S3_ENDPOINT} (${S3_REGION})`);
});
