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 { Stripe } from "stripe";
import { type AppConfig } from "../config.js";
import { type AdminTokenResult, verifyAdminSession } from "../helpers/adminTokens.js";
import { authenticateFromHeader } from "../helpers/auth.js";
import { readClientSessionCookie, verifyClientSession } from "../helpers/clientSessions.js";
import type { ClientEnv, MaybeClientEnv } from "../types/app.types.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 };
  }>;
  maybeClientAuthMiddleware: MiddlewareHandler<MaybeClientEnv>;
  clientAuthMiddleware: MiddlewareHandler<ClientEnv>;
  authMiddleware: MiddlewareHandler<{
    Variables: { supabase: SupabaseClient; user: User };
    Bindings: { user: User };
  }>;
  adminAuthMiddleware: MiddlewareHandler<{
    Variables: { adminSession: import("../helpers/adminTokens.js").AdminSessionClaims };
  }>;
  r2Middleware: MiddlewareHandler<{
    Variables: { s3_client: S3Client };
  }>;
  regularUserCheckMiddleware: MiddlewareHandler<{
    Variables: { supabase: SupabaseClient; user: User };
    Bindings: { user: User };
  }>;
  billingCheckoutAccessMiddleware: MiddlewareHandler<{
    Variables: { supabase: SupabaseClient; user: User };
    Bindings: { user: User };
  }>;
  activePlanAccessMiddleware: 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 isAdminTokenFailure = <T>(
      result: AdminTokenResult<T>
    ): result is Extract<AdminTokenResult<T>, { success: false }> => !result.success;

    const createProfileAccessMiddleware = () =>
      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_client")
          .eq("id", user.id)
          .single();

        if (error || !profile) {
          return c.json({ error: error?.message ?? "Profile not found" }, 500);
        }

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

        await next();
      });

    const loadClientFromCookie = async (
      cookieHeader: string | undefined,
      supabase: SupabaseClient
    ) => {
      const token = readClientSessionCookie(cookieHeader, {
        cookieName: config.CLIENT_AUTH_COOKIE_NAME,
      });

      if (!token) {
        return {
          client: null,
          error: "Client session required",
          success: false as const,
        };
      }

      try {
        const claims = verifyClientSession(token, {
          secret: config.CLIENT_AUTH_JWT_SECRET,
        });

        const { data: client, error } = await supabase
          .from("clients")
          .select("*")
          .eq("id", claims.sub)
          .maybeSingle();

        if (error || !client) {
          return {
            client: null,
            error: error?.message ?? "Client session required",
            success: false as const,
          };
        }

        return {
          client,
          error: null,
          success: true as const,
        };
      } catch (error) {
        return {
          client: null,
          error: error instanceof Error ? error.message : "Client session required",
          success: false as const,
        };
      }
    };

    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 adminAuthMiddleware = createMiddleware<{
      Variables: { adminSession: import("../helpers/adminTokens.js").AdminSessionClaims };
    }>(async (c, next) => {
      const authHeader = c.req.header("Authorization");

      if (!authHeader || !authHeader.startsWith("Bearer ")) {
        return c.json(
          {
            code: "ADMIN_SESSION_REQUIRED",
            error: "Admin session required",
          },
          401
        );
      }

      const sessionToken = authHeader.substring(7);
      const verifiedSession = verifyAdminSession(sessionToken, config);

      if (isAdminTokenFailure(verifiedSession)) {
        return c.json(
          {
            code: verifiedSession.code,
            error: verifiedSession.error,
          },
          verifiedSession.statusCode
        );
      }

      c.set("adminSession", verifiedSession.value);
      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 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 maybeClientAuthMiddleware = createMiddleware<MaybeClientEnv>(async (c, next) => {
      const supabase = c.get("supabase");
      c.set("client", null);

      const cookieHeader = c.req.header("Cookie");
      const result = await loadClientFromCookie(cookieHeader, supabase);

      if (result.success) {
        c.set("client", result.client);
      }

      await next();
    });

    const clientAuthMiddleware = createMiddleware<ClientEnv>(async (c, next) => {
      const supabase = c.get("supabase");
      const cookieHeader = c.req.header("Cookie");
      const result = await loadClientFromCookie(cookieHeader, supabase);

      if (!result.success) {
        return c.json({ error: result.error }, 401);
      }

      c.set("client", result.client);
      await next();
    });

    const regularUserCheckMiddleware = createProfileAccessMiddleware();
    const billingCheckoutAccessMiddleware = createProfileAccessMiddleware();
    const activePlanAccessMiddleware = 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: profileError } = await supabase
        .from("profiles")
        .select("organization_id")
        .eq("id", user.id)
        .single();

      if (profileError || !profile?.organization_id) {
        return c.json({ error: "Failed to resolve your organization" }, 500);
      }

      const { data: organizationProfiles, error: organizationProfilesError } = await supabase
        .from("profiles")
        .select("plan")
        .eq("organization_id", profile.organization_id);

      if (organizationProfilesError || !organizationProfiles) {
        return c.json({ error: "Failed to resolve organization plans" }, 500);
      }

      const hasAnyPlan = organizationProfiles.some(
        (organizationProfile) =>
          organizationProfile.plan && String(organizationProfile.plan).toLowerCase() !== "none"
      );

      if (!hasAnyPlan) {
        return c.json({ error: "An active subscription is required" }, 402);
      }

      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,
      adminAuthMiddleware,
      maybeAuthenticatedMiddleware,
      maybeClientAuthMiddleware,
      clientAuthMiddleware,
      r2Middleware,
      regularUserCheckMiddleware,
      billingCheckoutAccessMiddleware,
      activePlanAccessMiddleware,
      transporterMiddleware,
      stripeSyncMiddleware,
      stripeMiddleware,
    };
  }

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

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

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

  get clientAuth() {
    return this.middlewares.clientAuthMiddleware;
  }

  get maybeClientAuth() {
    return this.middlewares.maybeClientAuthMiddleware;
  }

  get adminAuth() {
    return this.middlewares.adminAuthMiddleware;
  }

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

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

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

  get billingCheckoutAccess() {
    return this.middlewares.billingCheckoutAccessMiddleware;
  }

  get activePlanAccess() {
    return this.middlewares.activePlanAccessMiddleware;
  }

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

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

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