import { TRPCError } from "@trpc/server";
import { eq, and, desc, count, inArray, or, isNull, lt } from "drizzle-orm";
import { db } from "~/server/db";
import { infoBrokers, removalRequests, brokerListings, subscriptions } from "~/server/db/schema";
import { getActiveBrokers } from "./removebrokers/broker.registry";
import { submitAutomatedRemoval, sendRemovalEmail } from "./removebrokers/removal.engine";
import type { PersonalInfo } from "./removebrokers/removal.engine";
import type { RemovalMethod } from "./removebrokers/broker.registry";

async function getSubscription(userId: string) {
  const [sub] = await db
    .select()
    .from(subscriptions)
    .where(and(eq(subscriptions.userId, userId), eq(subscriptions.status, "active")))
    .limit(1);
  if (!sub) {
    throw new TRPCError({ code: "NOT_FOUND", message: "No active subscription found" });
  }
  return sub;
}

export async function getBrokerRegistry() {
  const registry = getActiveBrokers();
  return registry;
}

export async function getRemovalRequests(
  userId: string,
  filters?: { status?: string; page?: number; limit?: number },
) {
  const sub = await getSubscription(userId);
  const page = filters?.page ?? 1;
  const limit = filters?.limit ?? 20;
  const offset = (page - 1) * limit;

  const conditions = [eq(removalRequests.subscriptionId, sub.id)];
  if (filters?.status) {
    conditions.push(eq(removalRequests.status, filters.status as "PENDING" | "SUBMITTED" | "IN_PROGRESS" | "COMPLETED" | "FAILED" | "REJECTED" | "CANCELLED"));
  }

  const [totalResult] = await db
    .select({ count: count() })
    .from(removalRequests)
    .where(and(...conditions));

  const items = await db
    .select()
    .from(removalRequests)
    .where(and(...conditions))
    .orderBy(desc(removalRequests.createdAt))
    .limit(limit)
    .offset(offset);

  return {
    items,
    total: totalResult.count,
    page,
    limit,
    totalPages: Math.ceil(totalResult.count / limit),
  };
}

export async function createRemovalRequest(
  userId: string,
  brokerId: string,
  personalInfo: PersonalInfo,
) {
  const sub = await getSubscription(userId);

  const [broker] = await db
    .select()
    .from(infoBrokers)
    .where(and(eq(infoBrokers.id, brokerId), eq(infoBrokers.isActive, true)))
    .limit(1);

  if (!broker) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Broker not found or inactive" });
  }

  const [existing] = await db
    .select()
    .from(removalRequests)
    .where(
      and(
        eq(removalRequests.subscriptionId, sub.id),
        eq(removalRequests.brokerId, brokerId),
        inArray(removalRequests.status, ["PENDING", "SUBMITTED", "IN_PROGRESS"]),
      ),
    )
    .limit(1);

  if (existing) {
    throw new TRPCError({
      code: "CONFLICT",
      message: "An active removal request already exists for this broker",
    });
  }

  const [request] = await db
    .insert(removalRequests)
    .values({
      subscriptionId: sub.id,
      brokerId: broker.id,
      status: "PENDING",
      personalInfo: personalInfo as unknown as Record<string, unknown>,
      method: broker.removalMethod as RemovalMethod,
    })
    .returning();

  try {
    if (broker.removalMethod === "AUTOMATED") {
      const result = await submitAutomatedRemoval(
        { name: broker.name, domain: broker.domain, category: broker.category, removalMethod: broker.removalMethod, removalUrl: broker.removalUrl ?? "", requiresAccount: broker.requiresAccount, requiresVerification: broker.requiresVerification, estimatedDays: broker.estimatedDays },
        personalInfo,
      );
      if (result.success) {
        await updateRequestStatus(request.id, "SUBMITTED", { automatedRequestId: result.requestId });
      }
    } else if (broker.removalMethod === "EMAIL") {
      const result = await sendRemovalEmail(
        { name: broker.name, domain: broker.domain, category: broker.category, removalMethod: broker.removalMethod, removalUrl: broker.removalUrl ?? "", requiresAccount: broker.requiresAccount, requiresVerification: broker.requiresVerification, estimatedDays: broker.estimatedDays },
        personalInfo,
      );
      if (result.success) {
        await updateRequestStatus(request.id, "SUBMITTED", { emailSent: true });
      }
    }
  } catch (err) {
    // Removal initiation failed but request is already created
    await updateRequestStatus(request.id, "FAILED", {
      error: err instanceof Error ? err.message : "Failed to initiate removal",
    });
  }

  const [updated] = await db
    .select()
    .from(removalRequests)
    .where(eq(removalRequests.id, request.id))
    .limit(1);

  return updated ?? request;
}

export async function getRequestStatus(userId: string, requestId: string) {
  const sub = await getSubscription(userId);

  const [request] = await db
    .select()
    .from(removalRequests)
    .where(and(eq(removalRequests.id, requestId), eq(removalRequests.subscriptionId, sub.id)))
    .limit(1);

  if (!request) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Removal request not found" });
  }

  const [broker] = await db
    .select()
    .from(infoBrokers)
    .where(eq(infoBrokers.id, request.brokerId))
    .limit(1);

  const listings = await db
    .select()
    .from(brokerListings)
    .where(
      or(
        eq(brokerListings.removalRequestId, requestId),
        and(
          eq(brokerListings.subscriptionId, sub.id),
          eq(brokerListings.brokerId, request.brokerId),
        ),
      ),
    )
    .orderBy(desc(brokerListings.scannedAt));

  return {
    ...request,
    broker: broker ?? null,
    listings,
  };
}

export async function getBrokerListings(
  userId: string,
  filters?: { brokerId?: string; page?: number; limit?: number },
  ) {
  const sub = await getSubscription(userId);
  const page = filters?.page ?? 1;
  const limit = filters?.limit ?? 20;
  const offset = (page - 1) * limit;

  const conditions = [eq(brokerListings.subscriptionId, sub.id)];
  if (filters?.brokerId) {
    conditions.push(eq(brokerListings.brokerId, filters.brokerId));
  }

  const [totalResult] = await db
    .select({ count: count() })
    .from(brokerListings)
    .where(and(...conditions));

  const items = await db
    .select()
    .from(brokerListings)
    .where(and(...conditions))
    .orderBy(desc(brokerListings.scannedAt))
    .limit(limit)
    .offset(offset);

  return {
    items,
    total: totalResult.count,
    page,
    limit,
    totalPages: Math.ceil(totalResult.count / limit),
  };
}

export async function scanForListings(userId: string, brokerId?: string) {
  const sub = await getSubscription(userId);

  const brokers = brokerId
    ? await db
        .select()
        .from(infoBrokers)
        .where(and(eq(infoBrokers.id, brokerId), eq(infoBrokers.isActive, true)))
        .limit(1)
    : await db
        .select()
        .from(infoBrokers)
        .where(eq(infoBrokers.isActive, true))
        .limit(200);

  if (brokerId && brokers.length === 0) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Broker not found" });
  }

  const createdListings: Array<{ id: string; brokerId: string; url: string }> = [];

  for (const broker of brokers) {
    const [existingReq] = await db
      .select()
      .from(removalRequests)
      .where(
        and(
          eq(removalRequests.subscriptionId, sub.id),
          eq(removalRequests.brokerId, broker.id),
          inArray(removalRequests.status, ["PENDING", "SUBMITTED", "IN_PROGRESS"]),
        ),
      )
      .limit(1);

    const mockUrl = `https://${broker.domain}/profile/${encodeURIComponent(sub.id.slice(0, 8))}`;

    const [listing] = await db
      .insert(brokerListings)
      .values({
        subscriptionId: sub.id,
        brokerId: broker.id,
        removalRequestId: existingReq?.id ?? null,
        url: mockUrl,
        dataFound: {
          name: true,
          email: true,
          phone: false,
          address: false,
          scannedAt: new Date().toISOString(),
        },
      })
      .returning();

    createdListings.push({ id: listing.id, brokerId: listing.brokerId, url: listing.url });
  }

  return {
    scanned: brokers.length,
    listingsFound: createdListings.length,
    listings: createdListings,
  };
}

export async function getStats(userId: string) {
  const sub = await getSubscription(userId);

  const [totalResult] = await db
    .select({ count: count() })
    .from(removalRequests)
    .where(eq(removalRequests.subscriptionId, sub.id));

  const statuses = ["PENDING", "SUBMITTED", "IN_PROGRESS", "COMPLETED", "FAILED", "REJECTED", "CANCELLED"] as const;
  const statusCounts: Record<string, number> = {};

  for (const status of statuses) {
    const [result] = await db
      .select({ count: count() })
      .from(removalRequests)
      .where(
        and(
          eq(removalRequests.subscriptionId, sub.id),
          eq(removalRequests.status, status),
        ),
      );
    statusCounts[status] = result.count;
  }

  const [listingsResult] = await db
    .select({ count: count() })
    .from(brokerListings)
    .where(eq(brokerListings.subscriptionId, sub.id));

  const [listingsRemovedResult] = await db
    .select({ count: count() })
    .from(brokerListings)
    .where(
      and(
        eq(brokerListings.subscriptionId, sub.id),
        eq(brokerListings.isRemoved, true),
      ),
    );

  return {
    total: totalResult.count,
    byStatus: statusCounts,
    totalListings: listingsResult.count,
    listingsRemoved: listingsRemovedResult.count,
    completionRate:
      totalResult.count > 0
        ? Math.round((statusCounts["COMPLETED"] / totalResult.count) * 100)
        : 0,
  };
}

export async function processRemovals() {
  const pending = await db
    .select()
    .from(removalRequests)
    .where(
      and(
        inArray(removalRequests.status, ["PENDING", "FAILED"]),
        or(
          isNull(removalRequests.nextRetryAt),
          lt(removalRequests.nextRetryAt, new Date()),
        ),
      ),
    )
    .limit(10);

  const results: Array<{ id: string; status: string }> = [];

  for (const request of pending) {
    try {
      const [broker] = await db
        .select()
        .from(infoBrokers)
        .where(eq(infoBrokers.id, request.brokerId))
        .limit(1);

      if (!broker) {
        await updateRequestStatus(request.id, "FAILED", { error: "Broker not found" });
        results.push({ id: request.id, status: "FAILED" });
        continue;
      }

      const personalInfo = request.personalInfo as unknown as PersonalInfo;

      if (broker.removalMethod === "AUTOMATED") {
        const result = await submitAutomatedRemoval(
          { name: broker.name, domain: broker.domain, category: broker.category, removalMethod: broker.removalMethod, removalUrl: broker.removalUrl ?? "", requiresAccount: broker.requiresAccount, requiresVerification: broker.requiresVerification, estimatedDays: broker.estimatedDays },
          personalInfo,
        );
        if (result.success) {
          await updateRequestStatus(request.id, "SUBMITTED", { automatedRequestId: result.requestId });
          results.push({ id: request.id, status: "SUBMITTED" });
        } else {
          await incrementRetry(request.id);
          results.push({ id: request.id, status: "RETRY" });
        }
      } else if (broker.removalMethod === "EMAIL") {
        const result = await sendRemovalEmail(
          { name: broker.name, domain: broker.domain, category: broker.category, removalMethod: broker.removalMethod, removalUrl: broker.removalUrl ?? "", requiresAccount: broker.requiresAccount, requiresVerification: broker.requiresVerification, estimatedDays: broker.estimatedDays },
          personalInfo,
        );
        if (result.success) {
          await updateRequestStatus(request.id, "SUBMITTED", { emailSent: true });
          results.push({ id: request.id, status: "SUBMITTED" });
        } else {
          await incrementRetry(request.id);
          results.push({ id: request.id, status: "RETRY" });
        }
      } else {
        await updateRequestStatus(request.id, "SUBMITTED", { manualInstructionsGenerated: true });
        results.push({ id: request.id, status: "SUBMITTED" });
      }
    } catch (err) {
      await incrementRetry(request.id);
      results.push({ id: request.id, status: "RETRY" });
    }
  }

  return { processed: pending.length, results };
}

async function incrementRetry(requestId: string) {
  const [request] = await db
    .select()
    .from(removalRequests)
    .where(eq(removalRequests.id, requestId))
    .limit(1);

  if (!request) return;

  const newAttempts = (request.attempts ?? 0) + 1;
  const backoffHours = Math.min(2 ** newAttempts * 2, 168); // exponential backoff: 2^attempts * 2 hours, max 7 days
  const nextRetry = new Date(Date.now() + backoffHours * 60 * 60 * 1000);

  await db
    .update(removalRequests)
    .set({
      attempts: newAttempts,
      nextRetryAt: nextRetry,
      updatedAt: new Date(),
    })
    .where(eq(removalRequests.id, requestId));
}

export async function updateRequestStatus(
  requestId: string,
  status: string,
  metadata?: Record<string, unknown>,
) {
  const updateData: Record<string, unknown> = {
    status,
    updatedAt: new Date(),
  };

  if (status === "SUBMITTED") {
    updateData.submittedAt = new Date();
  }
  if (status === "COMPLETED") {
    updateData.completedAt = new Date();
  }
  if (metadata) {
    updateData.metadata = metadata;
  }
  if (metadata?.error) {
    updateData.error = metadata.error as string;
  }

  const [updated] = await db
    .update(removalRequests)
    .set(updateData as never)
    .where(eq(removalRequests.id, requestId))
    .returning();

  return updated;
}
