/**
 * Organizations Domain Adapter
 *
 * Routes organizations, organization_members, and organization_invitations
 * CRUD to Prisma or Convex based on data-source config.
 * Maintains Prisma-compatible method signatures so service layer changes are minimal.
 */

import { prisma } from "@/lib/db";
import { convexQuery, convexMutation } from "@/lib/convex-client";
import { getDataSource } from "@/lib/data-source";
import {
  convexToPrismaShape,
  convexToPrismaShapeMany,
  applyPagination,
  type ConvexDoc,
} from "./base";

const DOMAIN = "organizations" as const;
const ORG_TS_FIELDS = ["createdAt", "updatedAt"];
const MEMBER_TS_FIELDS = ["joinedAt", "lastActiveAt"];
const INVITATION_TS_FIELDS = ["createdAt", "expiresAt", "sentAt", "acceptedAt"];

function isConvex(): boolean {
  return getDataSource(DOMAIN) === "convex";
}

// ===========================================================================
// Organizations
// ===========================================================================

const organizations = {
  // ─── findUnique ──────────────────────────────────────────────────────
  async findUnique(args: {
    where: { id?: string; ownerId?: string };
    include?: Record<string, unknown>;
    select?: Record<string, unknown>;
  }) {
    if (!isConvex()) {
      return prisma.organizations.findUnique(args as any);
    }

    const { id, ownerId } = args.where;

    if (id) {
      // Direct lookup by Convex ID
      const doc = await convexQuery<ConvexDoc | null>(
        "organizations/queries:getOrganization",
        { id },
      );
      if (!doc) return null;

      const shaped = convexToPrismaShape(doc, ORG_TS_FIELDS);

      // If include.members requested, fetch members separately
      if (args.include && (args.include as any).members) {
        const memberDocs =
          (await convexQuery<ConvexDoc[]>("organizations/queries:getMembers", {
            organizationId: (shaped as any).id,
          })) || [];
        (shaped as any).members = convexToPrismaShapeMany(
          memberDocs,
          MEMBER_TS_FIELDS,
        );
      }

      // _count support for getSeatConfiguration
      if (args.include && (args.include as any)._count) {
        const memberDocs =
          (await convexQuery<ConvexDoc[]>("organizations/queries:getMembers", {
            organizationId: (shaped as any).id,
          })) || [];
        (shaped as any)._count = { members: memberDocs.length };
      }

      return shaped;
    }

    if (ownerId) {
      const doc = await convexQuery<ConvexDoc | null>(
        "organizations/queries:getByOwnerId",
        { ownerId },
      );
      if (!doc) return null;
      return convexToPrismaShape(doc, ORG_TS_FIELDS);
    }

    return null;
  },

  // ─── findFirst ───────────────────────────────────────────────────────
  async findFirst(args: {
    where?: Record<string, unknown>;
    include?: Record<string, unknown>;
  }) {
    if (!isConvex()) {
      return prisma.organizations.findFirst(args as any);
    }

    // Handle specific lookups via dedicated Convex queries
    const where = args.where as Record<string, unknown> | undefined;

    if (where?.whatsappPhoneNumberId) {
      const doc = await convexQuery(
        "organizations/queries:getOrganizationByWhatsAppPhoneNumberId",
        { whatsappPhoneNumberId: where.whatsappPhoneNumberId as string }
      );
      return doc ? convexToPrismaShape(doc as ConvexDoc, ORG_TS_FIELDS) : null;
    }

    if (where?.twilioWhatsappNumber) {
      const doc = await convexQuery(
        "organizations/queries:getOrganizationByTwilioNumber",
        { twilioWhatsappNumber: where.twilioWhatsappNumber as string }
      );
      return doc ? convexToPrismaShape(doc as ConvexDoc, ORG_TS_FIELDS) : null;
    }

    if (where?.ownerId) {
      const doc = await convexQuery<ConvexDoc | null>(
        "organizations/queries:getByOwnerId",
        { ownerId: where.ownerId as string },
      );
      return doc ? convexToPrismaShape(doc, ORG_TS_FIELDS) : null;
    }

    // Fall back to prisma for other complex queries (e.g. whatsappVerifyToken)
    return prisma.organizations.findFirst(args as any);
  },

  // ─── create ──────────────────────────────────────────────────────────
  async create(args: {
    data: {
      id?: string;
      name: string;
      ownerId: string;
      stripeCustomerId?: string | null;
      includedSeats?: number;
      purchasedSeats?: number;
      subscriptionTier?: string;
    };
  }) {
    if (!isConvex()) {
      return prisma.organizations.create(args as any);
    }

    const d = args.data;
    const convexId = await convexMutation<string>(
      "organizations/mutations:createOrganization",
      {
        name: d.name,
        ownerId: d.ownerId,
        ...(d.subscriptionTier ? { subscriptionTier: d.subscriptionTier } : {}),
      },
    );

    const now = new Date();
    return {
      id: d.id || convexId || "",
      name: d.name,
      ownerId: d.ownerId,
      stripeCustomerId: d.stripeCustomerId ?? null,
      includedSeats: d.includedSeats ?? 1,
      purchasedSeats: d.purchasedSeats ?? 0,
      createdAt: now,
      updatedAt: now,
    };
  },

  // ─── update ──────────────────────────────────────────────────────────
  async update(args: { where: { id: string }; data: Record<string, unknown> }) {
    if (!isConvex()) {
      return prisma.organizations.update(args as any);
    }

    // Convex doesn't have a generic update mutation for organizations yet.
    // Fall back to prisma.
    return prisma.organizations.update(args as any);
  },

  // ─── count ───────────────────────────────────────────────────────────
  async count(args?: { where?: Record<string, unknown> }) {
    if (!isConvex()) {
      return prisma.organizations.count(args as any);
    }

    // No generic list-all query in Convex for organizations.
    return prisma.organizations.count(args as any);
  },
};

// ===========================================================================
// Organization Members
// ===========================================================================

const members = {
  // ─── findMany ────────────────────────────────────────────────────────
  async findMany(args: {
    where?: Record<string, unknown>;
    include?: Record<string, unknown>;
    orderBy?:
      | Record<string, string>
      | Record<string, string>[]
      | Array<Record<string, string>>;
    take?: number;
    skip?: number;
    select?: Record<string, unknown>;
  }) {
    if (!isConvex()) {
      return prisma.organization_members.findMany(args as any);
    }

    const where = (args.where || {}) as Record<string, unknown>;
    const organizationId = where.organizationId as string | undefined;

    if (!organizationId) {
      // Without organizationId, can't query Convex efficiently
      return prisma.organization_members.findMany(args as any);
    }

    const docs =
      (await convexQuery<ConvexDoc[]>("organizations/queries:getMembers", {
        organizationId,
      })) || [];

    // Apply additional where filters
    let filtered = docs;
    if (where.userId) {
      filtered = filtered.filter((d) => d.userId === where.userId);
    }
    if (where.role) {
      filtered = filtered.filter((d) => d.role === where.role);
    }

    const paginated = applyPagination(filtered, args.skip, args.take);
    return convexToPrismaShapeMany(paginated, MEMBER_TS_FIELDS);
  },

  // ─── findFirst ───────────────────────────────────────────────────────
  async findFirst(args: {
    where?: Record<string, unknown>;
    include?: Record<string, unknown>;
    select?: Record<string, unknown>;
  }) {
    if (!isConvex()) {
      return prisma.organization_members.findFirst(args as any);
    }

    const where = (args.where || {}) as Record<string, unknown>;
    const userId = where.userId as string | undefined;
    const organizationId = where.organizationId as string | undefined;

    // If we have organizationId, query members of that org
    if (organizationId) {
      const docs =
        (await convexQuery<ConvexDoc[]>("organizations/queries:getMembers", {
          organizationId,
        })) || [];

      const match = docs.find((doc) => {
        for (const [k, v] of Object.entries(where)) {
          const docVal = k === "id" ? doc.prismaId || doc._id : doc[k];
          if (docVal !== v) return false;
        }
        return true;
      });

      if (!match) return null;
      const shaped = convexToPrismaShape(match, MEMBER_TS_FIELDS);

      // Handle include.organization for getOrganizationByMember
      if (args.include && (args.include as any).organization) {
        const orgId = (match as any).organizationId as string;
        const orgDoc = await convexQuery<ConvexDoc | null>(
          "organizations/queries:getOrganization",
          { id: orgId },
        );
        if (orgDoc) {
          (shaped as any).organization = convexToPrismaShape(
            orgDoc,
            ORG_TS_FIELDS,
          );

          // Nested members include
          const orgInclude = (args.include as any).organization;
          if (
            orgInclude &&
            typeof orgInclude === "object" &&
            orgInclude.include?.members
          ) {
            const orgMembers =
              (await convexQuery<ConvexDoc[]>(
                "organizations/queries:getMembers",
                { organizationId: orgId },
              )) || [];
            ((shaped as any).organization as any).members =
              convexToPrismaShapeMany(orgMembers, MEMBER_TS_FIELDS);
          }
        }
      }

      return shaped;
    }

    // If only userId and no organizationId, we can't query Convex efficiently
    // (Convex getMembers requires organizationId).
    // Fall back to prisma.
    if (userId && !organizationId) {
      return prisma.organization_members.findFirst(args as any);
    }

    return null;
  },

  // ─── findUnique ──────────────────────────────────────────────────────
  async findUnique(args: {
    where: { id: string };
    include?: Record<string, unknown>;
  }) {
    if (!isConvex()) {
      return prisma.organization_members.findUnique(args as any);
    }

    // Convex doesn't have a direct getById for members.
    // Fall back to prisma for unique lookups by member ID.
    return prisma.organization_members.findUnique(args as any);
  },

  // ─── create ──────────────────────────────────────────────────────────
  async create(args: {
    data: {
      id?: string;
      organizationId: string;
      userId: string;
      role: string;
      joinedAt?: Date;
      invitedBy?: string | null;
    };
  }) {
    if (!isConvex()) {
      return prisma.organization_members.create(args as any);
    }

    const d = args.data;
    const convexId = await convexMutation<string>(
      "organizations/mutations:addMember",
      {
        organizationId: d.organizationId,
        userId: d.userId,
        role: d.role,
        ...(d.invitedBy ? { invitedBy: d.invitedBy } : {}),
      },
    );

    return {
      id: d.id || convexId || "",
      organizationId: d.organizationId,
      userId: d.userId,
      role: d.role,
      joinedAt: d.joinedAt ?? new Date(),
      invitedBy: d.invitedBy ?? null,
      lastActiveAt: null,
    };
  },

  // ─── update ──────────────────────────────────────────────────────────
  async update(args: { where: { id: string }; data: Record<string, unknown> }) {
    if (!isConvex()) {
      return prisma.organization_members.update(args as any);
    }

    // No generic member update mutation in Convex yet.
    // Fall back to prisma.
    return prisma.organization_members.update(args as any);
  },

  // ─── delete ──────────────────────────────────────────────────────────
  async deleteOne(args: { where: { id: string } }) {
    if (!isConvex()) {
      return prisma.organization_members.delete(args as any);
    }

    // Convex removeMember expects organizationId + userId, not member id.
    // Fall back to prisma for id-based deletes.
    return prisma.organization_members.delete(args as any);
  },

  // ─── count ───────────────────────────────────────────────────────────
  async count(args?: { where?: Record<string, unknown> }) {
    if (!isConvex()) {
      return prisma.organization_members.count(args as any);
    }

    const where = (args?.where || {}) as Record<string, unknown>;
    const organizationId = where.organizationId as string | undefined;

    if (!organizationId) {
      return prisma.organization_members.count(args as any);
    }

    const docs =
      (await convexQuery<ConvexDoc[]>("organizations/queries:getMembers", {
        organizationId,
      })) || [];

    return docs.length;
  },
};

// ===========================================================================
// Organization Invitations
// ===========================================================================

const invitations = {
  // ─── findMany ────────────────────────────────────────────────────────
  async findMany(args: {
    where?: Record<string, unknown>;
    orderBy?: Record<string, string> | Record<string, string>[];
    take?: number;
    skip?: number;
  }) {
    if (!isConvex()) {
      return prisma.organization_invitations.findMany(args as any);
    }

    const where = (args.where || {}) as Record<string, unknown>;
    const organizationId = where.organizationId as string | undefined;
    const status = where.status as string | undefined;

    if (!organizationId) {
      return prisma.organization_invitations.findMany(args as any);
    }

    const docs =
      (await convexQuery<ConvexDoc[]>("organizations/queries:getInvitations", {
        organizationId,
        ...(status ? { status } : {}),
      })) || [];

    // Apply additional where filters
    let filtered = docs;
    if (where.email) {
      filtered = filtered.filter((d) => d.email === where.email);
    }
    // Filter by expiresAt if { gt: Date }
    if (where.expiresAt && typeof where.expiresAt === "object") {
      const gt = (where.expiresAt as { gt?: Date }).gt;
      if (gt) {
        const gtMs = gt.getTime();
        filtered = filtered.filter((d) => (d.expiresAt as number) > gtMs);
      }
    }

    const paginated = applyPagination(filtered, args.skip, args.take);
    return convexToPrismaShapeMany(paginated, INVITATION_TS_FIELDS);
  },

  // ─── findFirst ───────────────────────────────────────────────────────
  async findFirst(args: { where?: Record<string, unknown> }) {
    if (!isConvex()) {
      return prisma.organization_invitations.findFirst(args as any);
    }

    const where = (args.where || {}) as Record<string, unknown>;
    const organizationId = where.organizationId as string | undefined;
    const status = where.status as string | undefined;

    if (!organizationId) {
      return prisma.organization_invitations.findFirst(args as any);
    }

    const docs =
      (await convexQuery<ConvexDoc[]>("organizations/queries:getInvitations", {
        organizationId,
        ...(status ? { status } : {}),
      })) || [];

    // Filter by all where conditions
    const match = docs.find((doc) => {
      for (const [k, v] of Object.entries(where)) {
        if (k === "id") {
          if ((doc.prismaId || doc._id) !== v) return false;
          continue;
        }
        if (k === "expiresAt" && typeof v === "object" && v !== null) {
          const gt = (v as { gt?: Date }).gt;
          if (gt && (doc.expiresAt as number) <= gt.getTime()) return false;
          continue;
        }
        if (doc[k] !== v) return false;
      }
      return true;
    });

    return match ? convexToPrismaShape(match, INVITATION_TS_FIELDS) : null;
  },

  // ─── findUnique ──────────────────────────────────────────────────────
  async findUnique(args: {
    where: { id?: string; token?: string };
    include?: Record<string, unknown>;
  }) {
    if (!isConvex()) {
      return prisma.organization_invitations.findUnique(args as any);
    }

    // Convex doesn't have a direct getById or getByToken for invitations.
    // Fall back to prisma for unique lookups.
    return prisma.organization_invitations.findUnique(args as any);
  },

  // ─── create ──────────────────────────────────────────────────────────
  async create(args: {
    data: {
      id?: string;
      organizationId: string;
      email: string;
      name?: string | null;
      role: string;
      token: string;
      status?: string;
      expiresAt: Date;
      invitedBy: string;
      sentAt?: Date;
    };
  }) {
    if (!isConvex()) {
      return prisma.organization_invitations.create(args as any);
    }

    // Convex doesn't have a createInvitation mutation yet.
    // Fall back to prisma.
    return prisma.organization_invitations.create(args as any);
  },

  // ─── update ──────────────────────────────────────────────────────────
  async update(args: { where: { id: string }; data: Record<string, unknown> }) {
    if (!isConvex()) {
      return prisma.organization_invitations.update(args as any);
    }

    // No Convex mutation for updating invitations yet.
    // Fall back to prisma.
    return prisma.organization_invitations.update(args as any);
  },

  // ─── count ───────────────────────────────────────────────────────────
  async count(args?: { where?: Record<string, unknown> }) {
    if (!isConvex()) {
      return prisma.organization_invitations.count(args as any);
    }

    const where = (args?.where || {}) as Record<string, unknown>;
    const organizationId = where.organizationId as string | undefined;
    const status = where.status as string | undefined;

    if (!organizationId) {
      return prisma.organization_invitations.count(args as any);
    }

    const docs =
      (await convexQuery<ConvexDoc[]>("organizations/queries:getInvitations", {
        organizationId,
        ...(status ? { status } : {}),
      })) || [];

    return docs.length;
  },
};

// ─── Namespace exports ────────────────────────────────────────────────────

export const organizationsAdapter = organizations;
export const organizationMembersAdapter = members;
export const organizationInvitationsAdapter = invitations;
