import { randomUUID } from "node:crypto";
import type {
  CreateKeywordSetInput,
  KeywordSet,
  UpdateKeywordSetInput,
} from "@shared/types";
import { and, eq } from "drizzle-orm";
import { db } from "../db";
import { keywordSets } from "../db/schema";
import { getProfileById } from "./profiles";
import { getSetting, setSetting } from "./settings";

const MAX_SETS_PER_OWNER = 30;
const MAX_TERMS_PER_SET = 100;

function parseTerms(raw: string): string[] {
  try {
    const parsed = JSON.parse(raw) as unknown;
    if (!Array.isArray(parsed)) return [];
    return parsed
      .filter((t): t is string => typeof t === "string")
      .map((t) => t.trim())
      .filter(Boolean)
      .slice(0, MAX_TERMS_PER_SET);
  } catch {
    return [];
  }
}

function mapRow(row: typeof keywordSets.$inferSelect): KeywordSet {
  return {
    id: row.id,
    name: row.name,
    terms: parseTerms(row.terms),
    isActive: row.isActive,
    createdAt: row.createdAt,
    updatedAt: row.updatedAt,
  };
}

async function countSetsForOwner(ownerProfileId: string): Promise<number> {
  const rows = await db
    .select({ id: keywordSets.id })
    .from(keywordSets)
    .where(eq(keywordSets.ownerProfileId, ownerProfileId));
  return rows.length;
}

async function clearActiveForOwner(ownerProfileId: string): Promise<void> {
  await db
    .update(keywordSets)
    .set({ isActive: false, updatedAt: new Date().toISOString() })
    .where(
      and(
        eq(keywordSets.ownerProfileId, ownerProfileId),
        eq(keywordSets.isActive, true),
      ),
    );
}

async function readGlobalSearchTermsFallback(): Promise<string[]> {
  const raw = await getSetting("searchTerms");
  if (!raw) {
    const envDefault = process.env.JOBSPY_SEARCH_TERMS || "web developer";
    return envDefault
      .split("|")
      .map((t) => t.trim())
      .filter(Boolean);
  }
  return parseTerms(raw);
}

async function defaultTermsForOwner(ownerProfileId: string): Promise<string[]> {
  if (ownerProfileId === "__default__") {
    return readGlobalSearchTermsFallback();
  }
  const profile = await getProfileById(ownerProfileId);
  const roles = profile?.data?.targetRoles ?? [];
  const fromProfile = roles
    .filter((role): role is string => typeof role === "string")
    .map((role) => role.trim())
    .filter(Boolean);
  if (fromProfile.length > 0) return fromProfile;
  return readGlobalSearchTermsFallback();
}

export async function ensureKeywordSetsForOwner(
  ownerProfileId: string,
): Promise<void> {
  const count = await countSetsForOwner(ownerProfileId);
  if (count > 0) return;

  const terms = await defaultTermsForOwner(ownerProfileId);
  const now = new Date().toISOString();
  await db.insert(keywordSets).values({
    id: randomUUID(),
    ownerProfileId,
    name: "Default",
    terms: JSON.stringify(terms),
    isActive: true,
    createdAt: now,
    updatedAt: now,
  });
}

export async function listKeywordSets(
  ownerProfileId: string,
): Promise<KeywordSet[]> {
  await ensureKeywordSetsForOwner(ownerProfileId);
  const rows = await db
    .select()
    .from(keywordSets)
    .where(eq(keywordSets.ownerProfileId, ownerProfileId))
    .orderBy(keywordSets.name);
  return rows.map(mapRow);
}

export async function getKeywordSetById(
  id: string,
  ownerProfileId: string,
): Promise<KeywordSet | null> {
  const [row] = await db
    .select()
    .from(keywordSets)
    .where(
      and(
        eq(keywordSets.id, id),
        eq(keywordSets.ownerProfileId, ownerProfileId),
      ),
    );
  return row ? mapRow(row) : null;
}

export async function getActiveKeywordSet(
  ownerProfileId: string,
): Promise<KeywordSet | null> {
  await ensureKeywordSetsForOwner(ownerProfileId);
  const [row] = await db
    .select()
    .from(keywordSets)
    .where(
      and(
        eq(keywordSets.ownerProfileId, ownerProfileId),
        eq(keywordSets.isActive, true),
      ),
    );
  if (row) return mapRow(row);

  const all = await listKeywordSets(ownerProfileId);
  return all[0] ?? null;
}

export async function getActiveKeywordSetTerms(
  ownerProfileId: string,
): Promise<string[] | null> {
  const active = await getActiveKeywordSet(ownerProfileId);
  if (!active) return null;
  return active.terms;
}

export async function createKeywordSet(
  ownerProfileId: string,
  input: CreateKeywordSetInput,
): Promise<KeywordSet> {
  await ensureKeywordSetsForOwner(ownerProfileId);
  const count = await countSetsForOwner(ownerProfileId);
  if (count >= MAX_SETS_PER_OWNER) {
    throw new Error(`Maximum of ${MAX_SETS_PER_OWNER} keyword sets per user`);
  }

  const name = input.name.trim();
  const terms = (input.terms ?? [])
    .map((t) => t.trim())
    .filter(Boolean)
    .slice(0, MAX_TERMS_PER_SET);
  const now = new Date().toISOString();
  const id = randomUUID();
  const shouldActivate = count === 0;

  if (shouldActivate) {
    await clearActiveForOwner(ownerProfileId);
  }

  await db.insert(keywordSets).values({
    id,
    ownerProfileId,
    name,
    terms: JSON.stringify(terms),
    isActive: shouldActivate,
    createdAt: now,
    updatedAt: now,
  });

  const created = await getKeywordSetById(id, ownerProfileId);
  if (!created) throw new Error("Failed to create keyword set");
  return created;
}

export async function updateKeywordSet(
  id: string,
  ownerProfileId: string,
  input: UpdateKeywordSetInput,
): Promise<KeywordSet | null> {
  const existing = await getKeywordSetById(id, ownerProfileId);
  if (!existing) return null;

  const updates: Partial<typeof keywordSets.$inferInsert> = {
    updatedAt: new Date().toISOString(),
  };
  if (input.name !== undefined) updates.name = input.name.trim();
  if (input.terms !== undefined) {
    updates.terms = JSON.stringify(
      input.terms
        .map((t) => t.trim())
        .filter(Boolean)
        .slice(0, MAX_TERMS_PER_SET),
    );
  }

  await db
    .update(keywordSets)
    .set(updates)
    .where(
      and(
        eq(keywordSets.id, id),
        eq(keywordSets.ownerProfileId, ownerProfileId),
      ),
    );

  const updated = await getKeywordSetById(id, ownerProfileId);
  if (
    updated?.isActive &&
    ownerProfileId === "__default__" &&
    input.terms !== undefined
  ) {
    await setSetting("searchTerms", JSON.stringify(updated.terms));
  }
  return updated;
}

export async function deleteKeywordSet(
  id: string,
  ownerProfileId: string,
): Promise<boolean> {
  const existing = await getKeywordSetById(id, ownerProfileId);
  if (!existing) return false;

  const count = await countSetsForOwner(ownerProfileId);
  if (count <= 1) {
    throw new Error("Cannot delete the last keyword set");
  }

  await db
    .delete(keywordSets)
    .where(
      and(
        eq(keywordSets.id, id),
        eq(keywordSets.ownerProfileId, ownerProfileId),
      ),
    );

  if (existing.isActive) {
    const remaining = await listKeywordSets(ownerProfileId);
    if (remaining[0]) {
      await activateKeywordSet(remaining[0].id, ownerProfileId);
    }
  }
  return true;
}

export async function activateKeywordSet(
  id: string,
  ownerProfileId: string,
): Promise<KeywordSet | null> {
  const existing = await getKeywordSetById(id, ownerProfileId);
  if (!existing) return null;

  const now = new Date().toISOString();
  await clearActiveForOwner(ownerProfileId);
  await db
    .update(keywordSets)
    .set({ isActive: true, updatedAt: now })
    .where(
      and(
        eq(keywordSets.id, id),
        eq(keywordSets.ownerProfileId, ownerProfileId),
      ),
    );

  const activated = await getKeywordSetById(id, ownerProfileId);
  if (activated && ownerProfileId === "__default__") {
    await setSetting("searchTerms", JSON.stringify(activated.terms));
  }
  return activated;
}

export async function syncActiveKeywordSetTerms(
  ownerProfileId: string,
  terms: string[],
): Promise<KeywordSet | null> {
  await ensureKeywordSetsForOwner(ownerProfileId);
  const active = await getActiveKeywordSet(ownerProfileId);
  if (!active) return null;
  return updateKeywordSet(active.id, ownerProfileId, { terms });
}
