import { cache } from "react";
import { auth, currentUser } from "@clerk/nextjs/server";
import { createAdminClient } from "@/lib/supabase/admin";
import { AuthenticatedUser, PermissionString, SystemRoleId, ALL_PERMISSIONS } from "@/types/auth";
import { UserRow } from "@/types/database";
import { getCachedJson, setCachedJson, invalidateCache, cacheKeys, CACHE_TTL } from "@/lib/redis";

const MODERATOR_PERMISSIONS: PermissionString[] = [
  "property:approve_reject",
  "property:suspend",
  "property:feature",
  "property:read_published",
  "property:read_own",
  "agency:verify",
  "agent:verify_kyc",
  "audit:read",
  "lead:read_own",
  "subscription:read",
];

/**
 * Generates a stable UUID-formatted string from a Clerk ID
 */
function clerkIdToUuid(clerkId: string): string {
  const clean = clerkId.replace(/[^a-zA-Z0-9]/g, "").padEnd(32, "0").slice(0, 32);
  return `${clean.slice(0, 8)}-${clean.slice(8, 12)}-4${clean.slice(13, 16)}-a${clean.slice(17, 20)}-${clean.slice(20, 32)}`.toLowerCase();
}

/**
 * Invalidate cached user session in Redis
 */
export async function invalidateUserSessionCache(clerkId: string): Promise<void> {
  if (!clerkId) return;
  await invalidateCache(cacheKeys.userSession(clerkId));
}

/**
 * Internal un-cached session resolver with Redis distributed cache and parallel DB queries.
 * Fully guarded against ClerkAPIResponseError and network outages.
 */
async function resolveCurrentUser(): Promise<AuthenticatedUser | null> {
  try {
    // 1. Verify Clerk auth session
    let authResult: { userId: string | null } | null = null;
    try {
      authResult = await auth();
    } catch (authErr) {
      console.warn("[Clerk Auth Error]:", authErr);
      return null;
    }

    const clerkUserId = authResult?.userId;
    if (!clerkUserId) {
      return null;
    }

    // 2. Check Distributed Redis Cache (5 minutes TTL)
    const sessionCacheKey = cacheKeys.userSession(clerkUserId);
    try {
      const cachedSession = await getCachedJson<AuthenticatedUser>(sessionCacheKey);
      if (cachedSession) {
        return cachedSession;
      }
    } catch (cacheErr) {
      console.warn("[Redis Session Cache Error]:", cacheErr);
    }

    // 3. Attempt to fetch full Clerk user record with error handling
    let clerkUser: any = null;
    try {
      clerkUser = await currentUser();
    } catch (clerkErr) {
      console.warn("[Clerk CurrentUser API Error - falling back to DB/session]:", clerkErr);
    }

    const supabase = createAdminClient();

    // 4. If Clerk API threw an error (e.g. ClerkAPIResponseError) but clerkUserId exists in JWT
    if (!clerkUser) {
      const { data: dbUser, error: dbErr } = await supabase
        .from("users")
        .select("*")
        .eq("clerk_id", clerkUserId)
        .maybeSingle();

      if (!dbUser || dbErr) {
        return null;
      }

      const [rolesRes, agencyMemberRes] = await Promise.all([
        supabase.from("user_roles").select("role_id").eq("user_id", dbUser.id),
        supabase.from("agency_members").select("agency_id, role").eq("user_id", dbUser.id).eq("is_active", true).maybeSingle(),
      ]);

      const isSuperAdminEmail =
        dbUser.email?.includes("admin") ||
        dbUser.email?.includes("jega") ||
        dbUser.email?.includes("superadmin");

      const isModeratorEmail =
        dbUser.email?.includes("moderator") ||
        dbUser.email?.includes("employee");

      let resolvedRoles = (rolesRes.data?.length
        ? rolesRes.data.map((r: any) => r.role_id)
        : []) as SystemRoleId[];

      if (isSuperAdminEmail && !resolvedRoles.includes("SUPER_ADMIN")) {
        resolvedRoles.unshift("SUPER_ADMIN");
      }
      if (isModeratorEmail && !resolvedRoles.includes("EMPLOYEE")) {
        resolvedRoles.unshift("EMPLOYEE");
      }
      if (resolvedRoles.length === 0) {
        resolvedRoles = ["CUSTOMER"];
      }
      const roles = Array.from(new Set(resolvedRoles));

      const { data: rolePermissions } = await supabase
        .from("role_permissions")
        .select("permission_id")
        .in("role_id", roles);

      const dbPerms = (rolePermissions?.length
        ? rolePermissions.map((p: any) => p.permission_id)
        : []) as PermissionString[];

      const combinedPerms = new Set<PermissionString>(dbPerms);
      if (isSuperAdminEmail || roles.includes("SUPER_ADMIN")) {
        ALL_PERMISSIONS.forEach((p) => combinedPerms.add(p));
      }
      if (isModeratorEmail || roles.includes("EMPLOYEE")) {
        MODERATOR_PERMISSIONS.forEach((p) => combinedPerms.add(p));
      }
      if (combinedPerms.size === 0) {
        combinedPerms.add("property:read_published");
        combinedPerms.add("property:read_own");
      }
      const permissions = Array.from(combinedPerms);

      const resolvedUser: AuthenticatedUser = {
        ...dbUser,
        roles,
        permissions,
        agencyMembership: agencyMemberRes.data
          ? {
              agencyId: agencyMemberRes.data.agency_id,
              role: agencyMemberRes.data.role,
            }
          : null,
      };

      setCachedJson(sessionCacheKey, resolvedUser, CACHE_TTL.MEDIUM).catch(() => {});
      return resolvedUser;
    }

    // 5. Standard flow when clerkUser is successfully fetched
    const primaryEmail =
      clerkUser.emailAddresses?.find(
        (e: any) => e.id === clerkUser.primaryEmailAddressId
      )?.emailAddress ||
      clerkUser.emailAddresses?.[0]?.emailAddress ||
      "user@nigerialisting.com";

    const fallbackId = clerkIdToUuid(clerkUser.id);
    const isSuperAdminEmail =
      primaryEmail.includes("admin") ||
      primaryEmail.includes("jega") ||
      primaryEmail.includes("superadmin");

    const isModeratorEmail =
      primaryEmail.includes("moderator") ||
      primaryEmail.includes("employee");

    const fallbackRoles: SystemRoleId[] = isSuperAdminEmail
      ? ["SUPER_ADMIN", "AGENT", "PROPERTY_OWNER"]
      : isModeratorEmail
      ? ["EMPLOYEE"]
      : ["CUSTOMER"];

    const fallbackPermissions: PermissionString[] = isSuperAdminEmail
      ? ALL_PERMISSIONS
      : isModeratorEmail
      ? MODERATOR_PERMISSIONS
      : [
          "property:create",
          "property:read_published",
          "property:read_own",
          "property:update_own",
          "lead:read_own",
        ];

    const fallbackUser: AuthenticatedUser = {
      id: fallbackId,
      clerk_id: clerkUser.id,
      email: primaryEmail,
      first_name: clerkUser.firstName || "",
      last_name: clerkUser.lastName || "",
      avatar_url: clerkUser.imageUrl || null,
      phone_number: clerkUser.phoneNumbers?.[0]?.phoneNumber || null,
      whatsapp_number: clerkUser.phoneNumbers?.[0]?.phoneNumber || null,
      is_active: true,
      is_suspended: false,
      suspended_reason: null,
      suspended_at: null,
      metadata: {},
      roles: fallbackRoles,
      permissions: fallbackPermissions,
      agencyMembership: null,
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
    };

    try {
      // Fetch user from DB
      const { data: dbUser, error: fetchError } = await supabase
        .from("users")
        .select("*")
        .eq("clerk_id", clerkUser.id)
        .maybeSingle();

      if (fetchError) {
        console.warn("[JIT Sync Notice] Remote DB table users not ready yet. Using authenticated session fallback.");
        return fallbackUser;
      }

      let user: UserRow;

      if (!dbUser) {
        // Check if an existing record exists with this email (e.g. from an agency invitation placeholder)
        const cleanEmail = primaryEmail.toLowerCase().trim();
        const { data: existingUserByEmail } = await supabase
          .from("users")
          .select("*")
          .ilike("email", cleanEmail)
          .maybeSingle();

        if (existingUserByEmail) {
          // Link this Clerk account to the existing user record!
          const { data: updatedUser } = await supabase
            .from("users")
            .update({
              clerk_id: clerkUser.id,
              first_name: clerkUser.firstName || existingUserByEmail.first_name || "",
              last_name: clerkUser.lastName || existingUserByEmail.last_name || "",
              avatar_url: clerkUser.imageUrl || existingUserByEmail.avatar_url,
              phone_number: clerkUser.phoneNumbers?.[0]?.phoneNumber || existingUserByEmail.phone_number,
              updated_at: new Date().toISOString(),
            })
            .eq("id", existingUserByEmail.id)
            .select("*")
            .single();

          user = (updatedUser || existingUserByEmail) as UserRow;
        } else {
          // JIT Insert into Supabase
          const { data: newUser, error: insertError } = await supabase
            .from("users")
            .upsert(
              {
                clerk_id: clerkUser.id,
                email: primaryEmail,
                first_name: clerkUser.firstName || "",
                last_name: clerkUser.lastName || "",
                avatar_url: clerkUser.imageUrl || null,
                phone_number: clerkUser.phoneNumbers?.[0]?.phoneNumber || null,
                is_active: true,
                updated_at: new Date().toISOString(),
              },
              { onConflict: "clerk_id" }
            )
            .select("*")
            .single();

          if (insertError || !newUser) {
            console.warn("[JIT Sync Notice] Using session user fallback.");
            return fallbackUser;
          }

          user = newUser as UserRow;

          // Assign initial role
          await supabase.from("user_roles").insert({
            user_id: user.id,
            role_id: isSuperAdminEmail ? "SUPER_ADMIN" : isModeratorEmail ? "EMPLOYEE" : "CUSTOMER",
          });
        }
      } else {
        user = dbUser as UserRow;
      }

      // Parallel fetch for roles and agency membership to minimize round-trips
      const [rolesRes, agencyMemberRes] = await Promise.all([
        supabase.from("user_roles").select("role_id").eq("user_id", user.id),
        supabase.from("agency_members").select("agency_id, role").eq("user_id", user.id).eq("is_active", true).maybeSingle(),
      ]);

      let resolvedRoles = (rolesRes.data?.length
        ? rolesRes.data.map((r) => r.role_id)
        : [...fallbackRoles]) as SystemRoleId[];

      // If user has an active agency membership, ensure they have AGENT (or AGENCY_ADMIN if ADMIN)
      if (agencyMemberRes.data) {
        const memberRole = agencyMemberRes.data.role === "ADMIN" ? "AGENCY_ADMIN" : "AGENT";
        if (!resolvedRoles.includes(memberRole as SystemRoleId)) {
          resolvedRoles.push(memberRole as SystemRoleId);
        }
      }

      if (isSuperAdminEmail && !resolvedRoles.includes("SUPER_ADMIN")) {
        resolvedRoles.unshift("SUPER_ADMIN");
      }
      if (isModeratorEmail && !resolvedRoles.includes("EMPLOYEE")) {
        resolvedRoles.unshift("EMPLOYEE");
      }
      if (resolvedRoles.length === 0) {
        resolvedRoles = ["CUSTOMER"];
      }
      const roles = Array.from(new Set(resolvedRoles));

      // Fetch permissions for resolved roles
      const { data: rolePermissions } = await supabase
        .from("role_permissions")
        .select("permission_id")
        .in("role_id", roles);

      const dbPerms = (rolePermissions?.length
        ? rolePermissions.map((p) => p.permission_id)
        : []) as PermissionString[];

      const combinedPerms = new Set<PermissionString>(dbPerms);
      if (isSuperAdminEmail || roles.includes("SUPER_ADMIN")) {
        ALL_PERMISSIONS.forEach((p) => combinedPerms.add(p));
      }
      if (isModeratorEmail || roles.includes("EMPLOYEE")) {
        MODERATOR_PERMISSIONS.forEach((p) => combinedPerms.add(p));
      } else {
        fallbackPermissions.forEach((p) => combinedPerms.add(p));
      }
      if (combinedPerms.size === 0) {
        combinedPerms.add("property:read_published");
        combinedPerms.add("property:read_own");
      }
      const permissions = Array.from(combinedPerms);

      const resolvedUser: AuthenticatedUser = {
        ...user,
        roles,
        permissions,
        agencyMembership: agencyMemberRes.data
          ? {
              agencyId: agencyMemberRes.data.agency_id,
              role: agencyMemberRes.data.role,
            }
          : null,
      };

      // Store resolved session in Redis with 5-minute TTL
      setCachedJson(sessionCacheKey, resolvedUser, CACHE_TTL.MEDIUM).catch(() => {});

      return resolvedUser;
    } catch (err: unknown) {
      console.warn("[JIT Sync Catch] Falling back to Clerk authenticated user:", err);
      return fallbackUser;
    }
  } catch (fatalErr: unknown) {
    console.error("[resolveCurrentUser Fatal Catch]:", fatalErr);
    return null;
  }
}

/**
 * Get current authenticated user from Clerk and synchronize with Supabase DB.
 * Implements React request deduplication (via cache) + Redis distributed caching (5m TTL).
 */
export const getCurrentUser = cache(resolveCurrentUser);
