/**
 * Club workflows - FDS §6.7.
 *
 * Decider/executor split:
 *   - domain/club/machine.ts        pure validation + effect emission
 *   - domain/club/apply-effects.ts  DB writes + settlement-outbox enqueue
 *   - this file                     loads context, runs decider, applies effects,
 *                                    plus payment provider charge (kept in orchestrator)
 */

import { inArray } from 'drizzle-orm';
import type { DrizzleClient } from '@/server/db/client.js';
import * as clubQueries from '@/server/db/queries/club.js';
import type { PaymentProvider } from '@/server/payments/provider.js';
import { vendors } from '@/server/db/schema.js';
import { decide } from '@/server/domain/club/machine.js';
import type { ClubMembershipState } from '@/server/domain/club/events.js';
import { applyEffects, loadMembershipContextRow } from '@/server/domain/club/apply-effects.js';

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface ClubDeps {
  db: DrizzleClient;
  payments: () => Promise<PaymentProvider>;
  storage: {
    bucket: R2Bucket;
    publicBase: string;
    uploadQr(purchaseId: string, token: string): Promise<string>;
  };
  qrSecret: string;
  settings: {
    /** Club settlement commission rate, e.g. 0.15 (15%). Default: 0.15 */
    clubSettlementRate: number;
  };
}

export interface EnsureMembershipInput {
  userId: string;
  vendorId: string;
  joinedViaOrderLineId: string;
}

// ---------------------------------------------------------------------------
// Typed error
// ---------------------------------------------------------------------------

export class ClubError extends Error {
  constructor(
    public readonly code: 'MEMBERSHIP_REQUIRED' | 'VENDOR_NOT_FOUND',
    message: string,
  ) {
    super(message);
    this.name = 'ClubError';
  }
}

function rejectClub(code: string, message: string): never {
  throw new ClubError(code as ClubError['code'], message);
}

// ---------------------------------------------------------------------------
// Ensure club membership (idempotent upsert) - FDS §6.2 post-purchase
// ---------------------------------------------------------------------------

export async function ensureClubMembership(
  deps: ClubDeps,
  input: EnsureMembershipInput,
): Promise<{ membershipId: string; isNew: boolean }> {
  const existing = await loadMembershipContextRow(deps.db, input.userId, input.vendorId);

  let state: ClubMembershipState;
  let membershipId: string | undefined;
  if (!existing) {
    state = 'NEW';
  } else if (!existing.isActive) {
    state = 'INACTIVE';
    membershipId = existing.id;
  } else {
    state = 'ACTIVE';
    membershipId = existing.id;
  }

  const result = decide(
    { kind: 'membership', context: { state, membershipId } },
    {
      kind: 'ensure_membership_requested',
      userId: input.userId,
      vendorId: input.vendorId,
      joinedViaOrderLineId: input.joinedViaOrderLineId,
      at: new Date(),
    },
  );
  if (!result.ok) rejectClub(result.error, result.message);

  const applied = await applyEffects({ db: deps.db }, result.effects);
  if (!applied.membershipId) throw new Error('membership effect did not produce id');
  return { membershipId: applied.membershipId, isNew: applied.isNewMembership ?? false };
}

// ---------------------------------------------------------------------------
// List memberships for user - enriched with vendor data (FDS §4.10)
// ---------------------------------------------------------------------------

export interface MembershipEnriched {
  id: string;
  userId: string;
  vendorId: string;
  joinedViaOrderLineId: string;
  joinedAt: Date;
  isActive: boolean;
  vendorDisplayName: string;
  vendorLogoUrl: string | null;
}

export async function listMembershipsByUser(
  deps: ClubDeps,
  userId: string,
): Promise<MembershipEnriched[]> {
  const { db } = deps;

  const memberships = await clubQueries.listUserMemberships(db, userId);
  if (memberships.length === 0) return [];

  const vendorIds = [...new Set(memberships.map((m) => m.vendorId))];
  const vendorRows =
    vendorIds.length > 0
      ? await db
          .select({
            id: vendors.id,
            displayName: vendors.displayName,
            logoUrl: vendors.logoUrl,
          })
          .from(vendors)
          .where(inArray(vendors.id, vendorIds))
      : [];
  const vendorById = new Map(vendorRows.map((v) => [v.id, v]));

  return memberships.map((m) => {
    const vendor = vendorById.get(m.vendorId);
    return {
      id: m.id,
      userId: m.userId,
      vendorId: m.vendorId,
      joinedViaOrderLineId: m.joinedViaOrderLineId,
      joinedAt: m.joinedAt,
      isActive: m.isActive,
      vendorDisplayName: vendor?.displayName ?? '',
      vendorLogoUrl: vendor?.logoUrl ?? null,
    };
  });
}
