import "server-only";

import { cache } from "react";
import { cookies } from "next/headers";
import { ID, Permission, Query, Role } from "node-appwrite";

import { createAdminClient, getCurrentUser } from "./server";
import { DATABASE_ID, TABLES, type TenantSettings } from "./schema";
import { ACTIVE_TENANT_COOKIE } from "./tenant-types";
import { getActiveTenantId } from "./tenant";

export type TenantRole = "owner" | "admin" | "member";

export type TenantContext = {
  user: { id: string; name: string; email: string };
  tenantId: string;
  role: TenantRole;
  settings: TenantSettings | null;
  memberCount: number;
};

function pickHighestRole(roles: string[]): TenantRole | null {
  if (roles.includes("owner")) return "owner";
  if (roles.includes("admin")) return "admin";
  if (roles.includes("member")) return "member";
  return null;
}

async function resolveFirstValidTenantId(userId: string): Promise<string | null> {
  try {
    const { users, tablesDB } = createAdminClient();
    const memberships = await users.listMemberships(userId);
    if (memberships.total === 0) return null;
    const teamIds = memberships.memberships.map((m) => m.teamId);

    // Prefer teams that already have settings, but fall back to any membership.
    const settings = await tablesDB.listRows({
      databaseId: DATABASE_ID,
      tableId: TABLES.tenantSettings,
      queries: [Query.equal("tenantId", teamIds), Query.limit(1)],
    });
    const fromSettings = (settings.rows[0] as unknown as { tenantId: string })?.tenantId;
    return fromSettings ?? teamIds[0] ?? null;
  } catch {
    return null;
  }
}

async function ensureSettings(
  tablesDB: ReturnType<typeof createAdminClient>["tablesDB"],
  tenantId: string,
  userId: string,
  teamName: string,
): Promise<TenantSettings> {
  const result = await tablesDB.listRows({
    databaseId: DATABASE_ID,
    tableId: TABLES.tenantSettings,
    queries: [Query.equal("tenantId", tenantId), Query.limit(1)],
  });
  if (result.rows.length > 0) return result.rows[0] as unknown as TenantSettings;

  // Settings row missing — create a minimal one so the user isn't looped back to onboarding.
  const created = await tablesDB.createRow(
    DATABASE_ID,
    TABLES.tenantSettings,
    ID.unique(),
    { tenantId, officeName: teamName, createdBy: userId, defaultCurrency: "TRY" },
    [
      Permission.read(Role.team(tenantId)),
      Permission.update(Role.team(tenantId, "owner")),
      Permission.update(Role.team(tenantId, "admin")),
      Permission.delete(Role.team(tenantId, "owner")),
    ],
  );
  return created as unknown as TenantSettings;
}

async function setTenantCookie(tenantId: string) {
  try {
    (await cookies()).set(ACTIVE_TENANT_COOKIE, tenantId, {
      path: "/",
      httpOnly: true,
      sameSite: "strict",
      secure: process.env.NODE_ENV === "production",
      maxAge: 60 * 60 * 24 * 365,
    });
  } catch { /* ignore */ }
}

export const requireTenant = cache(async function requireTenant(): Promise<TenantContext> {
  const user = await getCurrentUser();
  if (!user) throw new Error("UNAUTHENTICATED");

  let tenantId = await getActiveTenantId();

  const { tablesDB, teams } = createAdminClient();

  // Validate stored tenantId: team must exist AND user must be a member.
  if (tenantId) {
    try {
      const memberships = await teams.listMemberships(tenantId);
      const isMember = memberships.memberships.some((m) => m.userId === user.$id);
      if (!isMember) {
        try { (await cookies()).delete(ACTIVE_TENANT_COOKIE); } catch { /* ignore */ }
        tenantId = null;
      }
    } catch {
      // Team deleted or inaccessible — clear stale pointer.
      try { (await cookies()).delete(ACTIVE_TENANT_COOKIE); } catch { /* ignore */ }
      tenantId = null;
    }
  }

  if (!tenantId) {
    tenantId = await resolveFirstValidTenantId(user.$id);
    if (tenantId) await setTenantCookie(tenantId);
  }

  if (!tenantId) throw new Error("NO_TENANT");

  const [team, memberships] = await Promise.all([
    teams.get(tenantId),
    teams.listMemberships(tenantId),
  ]);
  const membership = memberships.memberships.find((m) => m.userId === user.$id);
  if (!membership) throw new Error("NOT_A_MEMBER");

  const role = pickHighestRole(membership.roles) ?? "member";
  const memberCount = memberships.memberships.filter((m) => m.confirm).length;

  let settings: TenantSettings | null = null;
  try {
    settings = await ensureSettings(tablesDB, tenantId, user.$id, team.name);
  } catch {
    settings = null;
  }

  return {
    user: { id: user.$id, name: user.name, email: user.email },
    tenantId,
    role,
    settings,
    memberCount,
  };
});

export function requireRole(ctx: TenantContext, allowed: TenantRole[]): void {
  if (!allowed.includes(ctx.role)) {
    throw new Error("FORBIDDEN");
  }
}
