import fp from "fastify-plugin";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { randomUUID } from "node:crypto";
import { Queue } from "bullmq";
import IORedis from "ioredis";
import { createDatabase, type Database } from "@app/database";
import { schema } from "@app/database";
import { createAamosClient, type AamosClient } from "@app/ai-contracts";
import { FeatureFlagService } from "@app/feature-flags";
import { StoreVerifier } from "@app/subscriptions";
import { createMailer, type Mailer } from "../lib/mailer.js";
import { createDefaultRegistry, type ConnectorRegistry } from "@app/connectors";
import type { AppConfig } from "../config.js";
import { ApiError } from "../lib/errors.js";

declare module "fastify" {
  interface FastifyInstance {
    config: AppConfig;
    db: Database;
    dbPool: { end(): Promise<void> };
    jobQueue: Queue;
    redis: IORedis;
    aamos: AamosClient;
    flags: FeatureFlagService;
    storeVerifier: StoreVerifier;
    mailer: Mailer;
    connectors: ConnectorRegistry;
  }
  interface FastifyRequest {
    correlationId: string;
  }
}

import { JOB_QUEUE_NAME } from "@app/shared-types";
export { JOB_QUEUE_NAME };

/**
 * Kärnplugin: db, redis/kö, AAMOS-klient, flaggor, correlation-id,
 * säkerhetsheaders och enhetlig felhantering.
 */
export const corePlugin = fp(async (app: FastifyInstance, opts: { config: AppConfig }) => {
  const { config } = opts;
  app.decorate("config", config);

  // --- Databas ---
  const { db, pool } = createDatabase(config.DATABASE_URL);
  app.decorate("db", db);
  app.decorate("dbPool", pool);
  app.addHook("onClose", async () => {
    await pool.end();
  });

  // --- Redis + jobbkö ---
  const redis = new IORedis(config.REDIS_URL, { maxRetriesPerRequest: null, lazyConnect: true });
  redis.on("error", (err) => app.log.warn({ err }, "Redis-fel"));
  app.decorate("redis", redis);
  const jobQueue = new Queue(JOB_QUEUE_NAME, {
    connection: redis,
    defaultJobOptions: {
      attempts: 3,
      backoff: { type: "exponential", delay: 2_000 },
      removeOnComplete: 1_000,
      removeOnFail: 5_000,
    },
  });
  app.decorate("jobQueue", jobQueue);
  app.addHook("onClose", async () => {
    await jobQueue.close();
    redis.disconnect();
  });

  // --- AAMOS (befintlig plattform; mock endast i dev/test) ---
  app.decorate(
    "aamos",
    createAamosClient({
      AAMOS_MODE: config.AAMOS_MODE,
      AAMOS_API_URL: config.AAMOS_API_URL,
      AAMOS_API_KEY: config.AAMOS_API_KEY,
      AAMOS_TIMEOUT_MS: String(config.AAMOS_TIMEOUT_MS),
    }),
  );

  // --- Feature flags ---
  app.decorate(
    "flags",
    new FeatureFlagService({
      loadAll: async () => {
        const rows = await db.select().from(schema.featureFlags);
        return rows.map((r) => ({
          key: r.key,
          enabled: r.enabled,
          rolloutPercent: r.rolloutPercent,
        }));
      },
    }),
  );

  // --- Butiksverifiering ---
  app.decorate(
    "storeVerifier",
    new StoreVerifier({
      mode: config.APP_STORE_MODE,
      appleBundleId: config.APPLE_BUNDLE_ID,
      googlePackageName: config.GOOGLE_PACKAGE_NAME,
    }),
  );

  // --- E-post (log-läge i dev; leverantör kopplas i fas 7) ---
  app.decorate(
    "mailer",
    createMailer(config.EMAIL_MODE, {
      host: config.SMTP_HOST,
      port: config.SMTP_PORT,
      secure: config.SMTP_SECURE,
      user: config.SMTP_USER,
      pass: config.SMTP_PASS,
      from: config.MAIL_FROM,
    }),
  );

  // --- Connectors ---
  app.decorate("connectors", createDefaultRegistry());

  // --- Correlation ID (spec §58) ---
  app.decorateRequest("correlationId", "");
  app.addHook("onRequest", async (req: FastifyRequest, reply: FastifyReply) => {
    const incoming = req.headers["x-correlation-id"];
    req.correlationId =
      typeof incoming === "string" && incoming.length <= 100 ? incoming : randomUUID();
    reply.header("x-correlation-id", req.correlationId);
  });

  // --- Säkerhetsheaders ---
  app.addHook("onSend", async (_req, reply) => {
    reply.header("x-content-type-options", "nosniff");
    reply.header("x-frame-options", "DENY");
    reply.header("referrer-policy", "no-referrer");
    reply.header("cache-control", "no-store");
  });

  // --- Enhetlig felhantering ---
  const alertWebhook = async (text: string): Promise<void> => {
    const url = process.env.ERROR_WEBHOOK_URL;
    if (!url) return;
    try {
      await fetch(url, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ text }),
        signal: AbortSignal.timeout(5000),
      });
    } catch {
      // Larmet får aldrig påverka svaret till klienten.
    }
  };

  app.setErrorHandler((err, req, reply) => {
    if (err instanceof ApiError) {
      reply.status(err.statusCode).send({
        error: {
          code: err.code,
          message: err.message,
          details: err.details,
          correlationId: req.correlationId,
        },
      });
      return;
    }
    // Fastify-inbyggda (t.ex. rate limit, felaktig JSON)
    const known = err as { statusCode?: unknown; message?: unknown };
    const status = typeof known.statusCode === "number" ? known.statusCode : 500;
    if (status >= 500) {
      req.log.error({ err, correlationId: req.correlationId }, "Ohanterat fel");
      void alertWebhook(
        `API 5xx: ${req.method} ${req.url} – ${String(known.message ?? "okänt fel")} (correlationId: ${req.correlationId})`,
      );
    }
    reply.status(status).send({
      error: {
        code: status === 429 ? "RATE_LIMITED" : status >= 500 ? "INTERNAL" : "REQUEST_ERROR",
        message:
          status >= 500
            ? "Internt fel – försök igen."
            : typeof known.message === "string"
              ? known.message
              : "Ogiltig förfrågan.",
        correlationId: req.correlationId,
      },
    });
  });

  app.setNotFoundHandler((req, reply) => {
    reply.status(404).send({
      error: {
        code: "NOT_FOUND",
        message: "Endpointen finns inte.",
        correlationId: req.correlationId,
      },
    });
  });
});
