import { S3Client } from "@aws-sdk/client-s3";
import type { StripeSync } from "@supabase/stripe-sync-engine";
import { createClient, type SupabaseClient, type User } from "@supabase/supabase-js";
import type { Context, MiddlewareHandler, Next } from "hono";
import { createMiddleware } from "hono/factory";
import type { Transporter } from "nodemailer";
import { StreamChat } from "stream-chat";
import { Stripe } from "stripe";
import { type AppConfig } from "../config.js";
import { authenticateFromHeader } from "../helpers/auth.js";
import { createStripeSync } from "./stripeSync.js";
import { createTransporter } from "./transporter.js";

export type Middlewares = {
  supabaseMiddleware: MiddlewareHandler<{
    Variables: { supabase: SupabaseClient };
  }>;
  basicAuthMiddleware: MiddlewareHandler<{
    Variables: { config: AppConfig };
  }>;
  maybeAuthenticatedMiddleware: MiddlewareHandler<{
    Variables: { supabase: SupabaseClient; user: User | null };
  }>;
  authMiddleware: MiddlewareHandler<{
    Variables: { supabase: SupabaseClient; user: User };
    Bindings: { user: User };
  }>;
  streamChatMiddleware: MiddlewareHandler<{
    Variables: { streamServerClient: StreamChat };
  }>;
  r2Middleware: MiddlewareHandler<{
    Variables: { s3_client: S3Client };
  }>;
  regularUserCheckMiddleware: MiddlewareHandler<{
    Variables: { supabase: SupabaseClient; user: User };
    Bindings: { user: User };
  }>;
  transporterMiddleware: MiddlewareHandler<{
    Variables: { transporter: Transporter };
  }>;
  stripeSyncMiddleware: MiddlewareHandler<{
    Variables: { stripeSync: StripeSync };
  }>;
  stripeMiddleware: MiddlewareHandler<{
    Variables: { stripe: Stripe };
  }>;
};

export class MiddlewareManager {
  private static instance: MiddlewareManager | null = null;
  private middlewares: Middlewares;

  private constructor(config: AppConfig) {
    this.middlewares = this.initializeMiddlewares(config);
  }

  static initialize(config: AppConfig): MiddlewareManager {
    if (MiddlewareManager.instance) {
      throw new Error("MiddlewareManager is already initialized");
    }
    MiddlewareManager.instance = new MiddlewareManager(config);
    return MiddlewareManager.instance;
  }

  static getInstance(): MiddlewareManager {
    if (!MiddlewareManager.instance) {
      throw new Error("MiddlewareManager is not initialized. Call initialize() first.");
    }
    return MiddlewareManager.instance;
  }

  private initializeMiddlewares(config: AppConfig): Middlewares {
    const supabaseMiddleware = createMiddleware(async (c: Context, next: Next) => {
      const supabase = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY);
      c.set("supabase", supabase);
      await next();
    });

    const basicAuthMiddleware = createMiddleware(async (c: Context, next: Next) => {
      if (config.TASKS_SECRET === "") {
        return c.json({ error: "Unauthorized: TASKS_SECRET is not set" }, 401);
      }
      const authHeader = c.req.header("Authorization");

      if (!authHeader || !authHeader.startsWith("Basic ")) {
        return c.json({ error: "Missing or invalid authorization header" }, 401);
      }
      const token = authHeader.substring(6); // Remove "Basic " prefix
      if (token !== config.TASKS_SECRET) {
        return c.json({ error: "Unauthorized" }, 401);
      }
      await next();
    });

    const authMiddleware = createMiddleware<{
      Variables: { supabase: SupabaseClient; user: User };
      Bindings: { user: User };
    }>(async (c, next) => {
      const supabase = c.get("supabase");
      const authHeader = c.req.header("Authorization");

      const authResult = await authenticateFromHeader(authHeader, supabase);

      if (!authResult.success) {
        return c.json(
          { error: (authResult as { success: false; error: string; statusCode: number }).error },
          (authResult as { success: false; error: string; statusCode: number }).statusCode as 401
        );
      }

      c.set("user", authResult.user);
      await next();
    });

    const maybeAuthenticatedMiddleware = createMiddleware<{
      Variables: { supabase: SupabaseClient; user: User | null };
    }>(async (c, next) => {
      const supabase = c.get("supabase");
      const authHeader = c.req.header("Authorization");

      c.set("user", null);

      if (authHeader && authHeader.startsWith("Bearer ")) {
        const token = authHeader.substring(7); // Remove "Bearer " prefix
        const {
          data: { user },
          error,
        } = await supabase.auth.getUser(token);

        if (!error && user) {
          c.set("user", user);
        }
      }

      await next();
    });

    const streamChatMiddleware = createMiddleware(async (c: Context, next: Next) => {
      const serverClient = StreamChat.getInstance(
        config.STREAM_CHAT_API_KEY,
        config.STREAM_CHAT_API_SECRET
      );
      c.set("streamServerClient", serverClient);
      await next();
    });

    const r2Middleware = createMiddleware(async (c: Context, next: Next) => {
      const s3 = new S3Client({
        region: "auto",
        endpoint: `https://${config.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
        credentials: {
          accessKeyId: config.R2_ACCESS_KEY_ID,
          secretAccessKey: config.R2_SECRET_ACCESS_KEY,
        },
      });
      c.set("s3_client", s3);
      await next();
    });

    const regularUserCheckMiddleware = createMiddleware<{
      Variables: { supabase: SupabaseClient; user: User };
      Bindings: { user: User };
    }>(async (c, next) => {
      const supabase = c.get("supabase");
      const user = c.get("user");

      const { data: profile, error } = await supabase
        .from("profiles")
        .select("is_temporary")
        .eq("id", user.id)
        .single();

      if (error || !profile) {
        return c.json({ error: error.message }, 500);
      }

      if (profile.is_temporary) {
        return c.json({ error: "User is read only" }, 401);
      }
      await next();
    });

    const transporterMiddleware = createMiddleware(async (c: Context, next: Next) => {
      const transporter = createTransporter(config);
      c.set("transporter", transporter);
      await next();
    });

    const stripeSyncMiddleware = createMiddleware(async (c: Context, next: Next) => {
      const stripeSync = createStripeSync(config);
      c.set("stripeSync", stripeSync);
      await next();
    });

    const stripeMiddleware = createMiddleware(async (c: Context, next: Next) => {
      const stripe = new Stripe(config.STRIPE_SECRET_KEY || "", {
        apiVersion: "2025-11-17.clover",
      });
      c.set("stripe", stripe);
      await next();
    });

    return {
      supabaseMiddleware,
      basicAuthMiddleware,
      authMiddleware,
      maybeAuthenticatedMiddleware,
      streamChatMiddleware,
      r2Middleware,
      regularUserCheckMiddleware,
      transporterMiddleware,
      stripeSyncMiddleware,
      stripeMiddleware,
    };
  }

  get supabase() {
    return this.middlewares.supabaseMiddleware;
  }

  get basicAuth() {
    return this.middlewares.basicAuthMiddleware;
  }

  get auth() {
    return this.middlewares.authMiddleware;
  }

  get maybeAuthenticated() {
    return this.middlewares.maybeAuthenticatedMiddleware;
  }

  get streamChat() {
    return this.middlewares.streamChatMiddleware;
  }

  get r2() {
    return this.middlewares.r2Middleware;
  }

  get regularUserCheck() {
    return this.middlewares.regularUserCheckMiddleware;
  }

  get transporter() {
    return this.middlewares.transporterMiddleware;
  }

  get stripeSync() {
    return this.middlewares.stripeSyncMiddleware;
  }

  get stripe() {
    return this.middlewares.stripeMiddleware;
  }
}
