import { getConnection } from '../../config/database.js';
import { findAll, findById, findByName, findMaxSortOrder, insert, update, updateSortOrder, softDelete } from '../repositories/stage.repo.js';
import { hasProductsAtOrPastStage } from '../repositories/orderProduct.repo.js';
import { createWithId } from '../utils/id.js';
import { NotFoundError, ConflictError } from '../utils/errors.js';
import { emitter } from '../events/emitter.js';
import type { Stage } from '../types/index.js';

// In-memory cache so hot paths (order stage advance, dashboard KPIs,
// reports, activity log rendering) can read the ordered stage list
// synchronously instead of awaiting a DB round trip on every call —
// mirrors how src/constants/stages.ts used to be a bare constant.
// Refreshed on every stage create/update/delete/reorder.
let cache: Stage[] = [];

export async function refreshCache(): Promise<void> {
  cache = await findAll();
}

export function getStages(): Stage[] {
  return cache;
}

export function getStageCount(): number {
  return cache.length;
}

export async function list(): Promise<Stage[]> {
  return findAll();
}

export async function get(id: string): Promise<Stage> {
  const item = await findById(id);
  if (!item) throw new NotFoundError('Stage not found');
  return item;
}

const DEFAULT_IDLE_THRESHOLD_MINUTES = 180;

export async function create(name: string, color: string, userId: string, idleThresholdMinutes?: number): Promise<Stage> {
  const duplicate = await findByName(name);
  if (duplicate) throw new ConflictError(`"${name}" already exists`);

  const maxOrder = await findMaxSortOrder();
  const { id } = await createWithId('stages', 'STGE', (id) => insert(
    { name, color, sort_order: maxOrder + 1, idle_threshold_minutes: idleThresholdMinutes ?? DEFAULT_IDLE_THRESHOLD_MINUTES },
    id, userId,
  ));
  const item = await get(id);
  emitter.emit('activity.create', { entity_type: 'stages', entity_id: id, action: 'created', user_id: userId });
  await refreshCache();
  return item;
}

export async function updateItem(id: string, name: string, color: string, userId: string, idleThresholdMinutes?: number): Promise<Stage> {
  const existing = await findById(id);
  if (!existing) throw new NotFoundError('Stage not found');
  const duplicate = await findByName(name, id);
  if (duplicate) throw new ConflictError(`"${name}" already exists`);
  await update(id, { name, color, idle_threshold_minutes: idleThresholdMinutes ?? existing.idle_threshold_minutes }, userId);
  emitter.emit('activity.create', { entity_type: 'stages', entity_id: id, action: 'updated', user_id: userId });
  await refreshCache();
  return get(id);
}

// Reorders the full stage list to match the given id order. This changes
// what position (and therefore what order_products.completed_stages
// integer) each stage occupies going forward — any order currently mid-way
// through production will display against the *new* order after this call,
// since progress is stored as a position, not a stage identity. That's an
// accepted tradeoff of making stage order editable at all.
export async function reorder(orderedIds: string[], userId: string): Promise<Stage[]> {
  const existing = await findAll();
  if (orderedIds.length !== existing.length || !existing.every((s) => orderedIds.includes(s.id))) {
    throw new ConflictError('Reorder list must contain exactly the current set of stages');
  }

  const conn = await getConnection();
  try {
    await conn.beginTransaction();
    for (let i = 0; i < orderedIds.length; i++) {
      await updateSortOrder(orderedIds[i]!, i + 1, userId, conn);
    }
    await conn.commit();
  } catch (err) {
    await conn.rollback();
    throw err;
  } finally {
    conn.release();
  }

  emitter.emit('activity.create', { entity_type: 'stages', entity_id: 'reorder', action: 'updated', user_id: userId });
  await refreshCache();
  return findAll();
}

export async function remove(id: string, userId: string): Promise<void> {
  const existing = await findById(id);
  if (!existing) throw new NotFoundError('Stage not found');

  // A stage still in use — any order line currently at or past this
  // stage's position — can't be deleted, otherwise its completed_stages
  // integer would point at a stage that no longer exists (or silently
  // shift onto the next one).
  if (await hasProductsAtOrPastStage(existing.sort_order)) {
    throw new ConflictError('This stage is in use by existing orders and cannot be deleted');
  }

  await softDelete(id, userId);
  emitter.emit('activity.create', { entity_type: 'stages', entity_id: id, action: 'deleted', user_id: userId });
  await refreshCache();
}
