import type { TxDrizzleClient } from '@/server/db/client.js';
import { affiliateEnrollments, ledgerEntries, users } from '@/server/db/schema.js';
import { and, eq } from 'drizzle-orm';
import { accrueCommission } from '@platform-modules/affiliate';
import { affiliateEntriesTable } from '@platform-modules/affiliate/schema';
import { setEntryWithdrawableAt } from '@platform-modules/ledger/vesting';
import {
  appendAdjustmentInTx,
  asModuleMoneyTx,
  recomputeOwnerWithdrawable,
} from '@/server/affiliate-module/host-retained.js';
import { asUserId, toModuleRef } from '@/server/platform-seams/ids.js';

export async function createMatureAffiliateEarningFixture(
  db: TxDrizzleClient,
  values: {
    userId: string;
    amountAgorot: number;
    sourceId: string;
    matureAt: Date;
  },
): Promise<{ sourceId: string; amountAgorot: number } | null> {
  return db.transaction(async (tx) => {
    const ownerId = toModuleRef(asUserId(values.userId));
    const findExisting = async () => {
      const [entry] = await tx
        .select({
          entryId: affiliateEntriesTable.entryId,
          amountAgorot: ledgerEntries.delta,
        })
        .from(affiliateEntriesTable)
        .innerJoin(ledgerEntries, eq(ledgerEntries.id, affiliateEntriesTable.entryId))
        .where(
          and(
            eq(affiliateEntriesTable.ownerId, ownerId),
            eq(affiliateEntriesTable.entryType, 'affiliate_commission'),
            eq(affiliateEntriesTable.sourceType, 'journey_e2e'),
            eq(affiliateEntriesTable.sourceId, values.sourceId),
          ),
        )
        .limit(1);
      return entry ? { sourceId: values.sourceId, amountAgorot: Number(entry.amountAgorot) } : null;
    };

    const prior = await findExisting();
    if (prior) return prior;

    const [eligibleUser] = await tx
      .select({ userId: users.id })
      .from(users)
      .innerJoin(
        affiliateEnrollments,
        and(eq(affiliateEnrollments.userId, users.id), eq(affiliateEnrollments.status, 'active')),
      )
      .where(and(eq(users.id, values.userId), eq(users.accountState, 'ACTIVE')))
      .limit(1);

    if (!eligibleUser) return null;

    const result = await accrueCommission(asModuleMoneyTx(tx), {
      userId: ownerId,
      amountMinor: BigInt(values.amountAgorot),
      entryType: 'affiliate_commission',
      sourceType: 'journey_e2e',
      sourceId: values.sourceId,
      matureAt: values.matureAt,
      memo: 'Journey E2E affiliate balance',
    });
    if (!result.inserted) return findExisting();
    if (result.state !== 'matured') return null;

    const [entry] = await tx
      .select({ entryId: affiliateEntriesTable.entryId })
      .from(affiliateEntriesTable)
      .where(
        and(
          eq(affiliateEntriesTable.ownerId, ownerId),
          eq(affiliateEntriesTable.sourceType, 'journey_e2e'),
          eq(affiliateEntriesTable.sourceId, values.sourceId),
        ),
      )
      .limit(1);
    if (!entry) throw new Error('E2E_AFFILIATE_EARNING_ENTRY_NOT_FOUND');

    await setEntryWithdrawableAt(asModuleMoneyTx(tx), {
      entryId: entry.entryId,
      withdrawableAt: values.matureAt,
    });
    await recomputeOwnerWithdrawable(tx, ownerId);
    return { sourceId: values.sourceId, amountAgorot: values.amountAgorot };
  });
}

const hostRetainedDeps = { now: () => new Date() };

async function requireActiveAffiliateUser(tx: TxDrizzleClient, userId: string) {
  const [eligibleUser] = await tx
    .select({ userId: users.id })
    .from(users)
    .innerJoin(
      affiliateEnrollments,
      and(eq(affiliateEnrollments.userId, users.id), eq(affiliateEnrollments.status, 'active')),
    )
    .where(and(eq(users.id, userId), eq(users.accountState, 'ACTIVE')))
    .limit(1);
  return eligibleUser ?? null;
}

export async function seedAffiliateLedgerFixture(
  db: TxDrizzleClient,
  values: {
    userId: string;
    amountAgorot: number;
    mature?: boolean;
    sourceId: string;
  },
): Promise<{ sourceId: string; amountAgorot: number } | null> {
  return db.transaction(async (tx) => {
    const eligibleUser = await requireActiveAffiliateUser(tx, values.userId);
    if (!eligibleUser) return null;

    const ownerId = toModuleRef(asUserId(values.userId));
    const matureAt = values.mature
      ? new Date(Date.now() - 86_400_000)
      : new Date(Date.now() + 86_400_000);

    if (values.amountAgorot < 0) {
      const inserted = await appendAdjustmentInTx(tx, hostRetainedDeps, {
        userId: values.userId,
        amountMinor: BigInt(values.amountAgorot),
        sourceType: 'journey_e2e',
        sourceId: values.sourceId,
        memo: 'Journey E2E affiliate balance',
      });
      if (!inserted) return null;
      return { sourceId: values.sourceId, amountAgorot: values.amountAgorot };
    }

    const result = await accrueCommission(asModuleMoneyTx(tx), {
      userId: ownerId,
      amountMinor: BigInt(values.amountAgorot),
      entryType: 'affiliate_commission',
      sourceType: 'journey_e2e',
      sourceId: values.sourceId,
      matureAt,
      memo: 'Journey E2E affiliate balance',
    });
    if (!result.inserted) {
      const [entry] = await tx
        .select({ amountAgorot: ledgerEntries.delta })
        .from(affiliateEntriesTable)
        .innerJoin(ledgerEntries, eq(ledgerEntries.id, affiliateEntriesTable.entryId))
        .where(
          and(
            eq(affiliateEntriesTable.ownerId, ownerId),
            eq(affiliateEntriesTable.sourceType, 'journey_e2e'),
            eq(affiliateEntriesTable.sourceId, values.sourceId),
          ),
        )
        .limit(1);
      return entry ? { sourceId: values.sourceId, amountAgorot: Number(entry.amountAgorot) } : null;
    }

    if (values.mature) {
      const [entry] = await tx
        .select({ entryId: affiliateEntriesTable.entryId })
        .from(affiliateEntriesTable)
        .where(
          and(
            eq(affiliateEntriesTable.ownerId, ownerId),
            eq(affiliateEntriesTable.sourceType, 'journey_e2e'),
            eq(affiliateEntriesTable.sourceId, values.sourceId),
          ),
        )
        .limit(1);
      if (!entry) throw new Error('E2E_AFFILIATE_LEDGER_ENTRY_NOT_FOUND');
      await setEntryWithdrawableAt(asModuleMoneyTx(tx), {
        entryId: entry.entryId,
        withdrawableAt: matureAt,
      });
      await recomputeOwnerWithdrawable(tx, ownerId);
    }

    return { sourceId: values.sourceId, amountAgorot: values.amountAgorot };
  });
}
