import type { DB } from "../generated/kysely-tailordb";
import {
  ValidationErrorError,
  InvalidScopeError,
  UnauthorizedError,
} from "../lib/errors.generated";
import {
  ok,
  err,
  requirePermission,
  type ReadonlyDB,
  type QueryContext,
} from "@tailor-platform/erp-kit/module";

const VALID_SCOPES = ["COMPANY", "GLOBAL", "ALL"];

interface GroupBy {
  entityType?: boolean;
  operationType?: boolean;
  actorType?: boolean;
  timePeriod?: "DAY" | "WEEK" | "MONTH";
}

export interface GetAuditSummaryInput {
  groupBy: GroupBy;
  filters?: {
    entityType?: string;
    operationType?: string;
    actorType?: string;
    companyId?: string;
    dateFrom?: Date;
    dateTo?: Date;
  };
  scope?: string;
}

interface SummaryEntry {
  entityType?: string;
  operationType?: string;
  actorType?: string;
  timePeriod?: string;
  count: number;
}

function formatTimePeriod(date: Date, period: "DAY" | "WEEK" | "MONTH"): string {
  const d = new Date(date);
  if (period === "DAY") {
    return d.toISOString().slice(0, 10);
  }
  if (period === "WEEK") {
    // Get Monday of the week
    const day = d.getDay();
    const diff = d.getDate() - day + (day === 0 ? -6 : 1);
    const monday = new Date(d);
    monday.setDate(diff);
    return monday.toISOString().slice(0, 10);
  }
  // MONTH
  return d.toISOString().slice(0, 7);
}

function buildGroupKey(entry: Record<string, unknown>, groupBy: GroupBy): string {
  const parts: string[] = [];
  if (groupBy.entityType) parts.push(`entityType:${String(entry.entityType)}`);
  if (groupBy.operationType) parts.push(`operationType:${String(entry.operationType)}`);
  if (groupBy.actorType) parts.push(`actorType:${String(entry.actorType)}`);
  if (groupBy.timePeriod) {
    const ts =
      entry.timestamp instanceof Date ? entry.timestamp : new Date(entry.timestamp as string);
    parts.push(`timePeriod:${formatTimePeriod(ts, groupBy.timePeriod)}`);
  }
  return parts.join("|");
}

function parseGroupKey(key: string, groupBy: GroupBy): Omit<SummaryEntry, "count"> {
  const result: Omit<SummaryEntry, "count"> = {};
  const parts = key.split("|");
  for (const part of parts) {
    const [field, value] = part.split(":");
    if (field === "entityType" && groupBy.entityType) result.entityType = value;
    if (field === "operationType" && groupBy.operationType) result.operationType = value;
    if (field === "actorType" && groupBy.actorType) result.actorType = value;
    if (field === "timePeriod" && groupBy.timePeriod) result.timePeriod = value;
  }
  return result;
}

export async function run(db: ReadonlyDB<DB>, input: GetAuditSummaryInput, ctx: QueryContext) {
  const permCheck = requirePermission(ctx, "audit:viewAuditSummary");
  if (!permCheck.ok) return permCheck;

  const { groupBy } = input;

  // Validate at least one grouping dimension
  if (!groupBy.entityType && !groupBy.operationType && !groupBy.actorType && !groupBy.timePeriod) {
    return err(new ValidationErrorError("at least one grouping dimension required"));
  }

  // Validate scope
  if (input.scope && !VALID_SCOPES.includes(input.scope)) {
    return err(new InvalidScopeError(input.scope));
  }
  if (input.scope === "COMPANY" && !input.filters?.companyId) {
    return err(new ValidationErrorError("scope=COMPANY requires companyId"));
  }

  let query = db.selectFrom("AuditEntry").selectAll();

  // Apply filters
  if (input.filters?.entityType) query = query.where("entityType", "=", input.filters.entityType);
  if (input.filters?.operationType)
    query = query.where("operationType", "=", input.filters.operationType);
  if (input.filters?.actorType) query = query.where("actorType", "=", input.filters.actorType);
  if (input.filters?.dateFrom) query = query.where("timestamp", ">=", input.filters.dateFrom);
  if (input.filters?.dateTo) query = query.where("timestamp", "<=", input.filters.dateTo);

  // Apply company-scope restriction from context
  if (ctx.companyId !== undefined) {
    // Reject if caller requests a company they are not authorized for
    if (
      ctx.companyId !== null &&
      input.filters?.companyId &&
      input.filters.companyId !== ctx.companyId
    ) {
      return err(new UnauthorizedError(ctx.actorId));
    }
    if (ctx.companyId === null) {
      // Global-scoped caller: cannot see company-scoped entries
      if (input.scope === "COMPANY") {
        return ok({ summary: [] as SummaryEntry[] });
      }
      query = query.where("companyId", "is", null);
    } else {
      // Company-scoped caller: cannot see global entries
      if (input.scope === "GLOBAL") {
        return ok({ summary: [] as SummaryEntry[] });
      }
      query = query.where("companyId", "=", ctx.companyId);
    }
  } else {
    // No scope restriction from context; apply input scope filtering
    if (input.scope === "COMPANY" || (!input.scope && input.filters?.companyId)) {
      query = query.where("companyId", "=", input.filters!.companyId!);
    } else if (input.scope === "GLOBAL" || (!input.scope && !input.filters?.companyId)) {
      query = query.where("companyId", "is", null);
    } else if (input.scope === "ALL" && input.filters?.companyId) {
      // scope=ALL with companyId: return combined company + global results
      query = query.where((eb) =>
        eb.or([eb("companyId", "=", input.filters!.companyId!), eb("companyId", "is", null)]),
      );
    }
    // scope=ALL without companyId: no companyId filter (return all scopes)
  }

  const entries = await query.execute();

  // Aggregate in-memory
  const groups = new Map<string, number>();
  for (const entry of entries) {
    const key = buildGroupKey(entry as unknown as Record<string, unknown>, groupBy);
    groups.set(key, (groups.get(key) ?? 0) + 1);
  }

  const summary: SummaryEntry[] = Array.from(groups.entries()).map(([key, count]) => ({
    ...parseGroupKey(key, groupBy),
    count,
  }));

  return ok({ summary });
}
