import type { WorkamajigSource, WmjUser, WmjClient, WmjProject, WmjAssignment } from "./types";

/**
 * Live Workamajig REST client.
 *
 * Workamajig's API (help.workamajig.com/api) authenticates every request with two
 * headers: `APIAccessToken` (issued per-account under Admin > Integrations > API)
 * and `UserToken` (issued for the API user). Base path is https://<host>/api/<version>/.
 *
 * Field names below follow Workamajig's documented resources (users, projects,
 * timesheets). Because reports vary per account, each mapper is deliberately
 * defensive and lives in one place so you can adjust it after a first real pull.
 */
export class WorkamajigApi implements WorkamajigSource {
  private base: string;
  private headers: Record<string, string>;

  constructor(opts?: { host?: string; version?: string; accessToken?: string; userToken?: string }) {
    const host = opts?.host ?? process.env.WORKAMAJIG_HOST;
    const version = opts?.version ?? process.env.WORKAMAJIG_API_VERSION ?? "beta1";
    const accessToken = opts?.accessToken ?? process.env.WORKAMAJIG_ACCESS_TOKEN;
    const userToken = opts?.userToken ?? process.env.WORKAMAJIG_USER_TOKEN;
    if (!host || !accessToken || !userToken) {
      throw new Error("Workamajig API not configured: set WORKAMAJIG_HOST, WORKAMAJIG_ACCESS_TOKEN, WORKAMAJIG_USER_TOKEN");
    }
    this.base = `${host.replace(/\/$/, "")}/api/${version}`;
    this.headers = {
      APIAccessToken: accessToken,
      UserToken: userToken,
      Accept: "application/json",
      "Content-Type": "application/json",
    };
  }

  private async get<T = unknown>(path: string, params: Record<string, string | number> = {}): Promise<T> {
    const url = new URL(`${this.base}/${path}`);
    for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v));
    const res = await fetch(url, { headers: this.headers, cache: "no-store" });
    if (!res.ok) throw new Error(`Workamajig ${path} -> ${res.status} ${await res.text()}`);
    return (await res.json()) as T;
  }

  /** Workamajig responses wrap rows in `data` and use `total`/`offset` style paging. */
  private async getAll<T>(path: string, params: Record<string, string | number> = {}): Promise<T[]> {
    const pageSize = 500;
    let offset = 0;
    const out: T[] = [];
    // eslint-disable-next-line no-constant-condition
    while (true) {
      const body = await this.get<{ data?: T[] | { [k: string]: T[] } } & Record<string, unknown>>(path, {
        ...params,
        limit: pageSize,
        offset,
      });
      const raw = body.data;
      const rows: T[] = Array.isArray(raw) ? raw : raw ? (Object.values(raw)[0] as T[]) ?? [] : [];
      out.push(...rows);
      if (rows.length < pageSize) break;
      offset += pageSize;
    }
    return out;
  }

  async users(): Promise<WmjUser[]> {
    const rows = await this.getAll<Record<string, unknown>>("users");
    return rows.map((r) => ({
      userKey: str(r.userKey ?? r.userID ?? r.id),
      firstName: str(r.firstName),
      lastName: str(r.lastName),
      email: str(r.email ?? r.userEmail).toLowerCase(),
      title: opt(r.title),
      department: opt(r.department ?? r.departmentName),
      officeName: opt(r.office ?? r.officeName ?? r.companyName),
      isActive: (r.isActive ?? r.active ?? true) !== false,
    }));
  }

  async clients(): Promise<WmjClient[]> {
    const rows = await this.getAll<Record<string, unknown>>("companies", { type: "client" });
    return rows.map((r) => ({
      clientKey: str(r.companyKey ?? r.companyID ?? r.clientKey),
      clientName: str(r.companyName ?? r.clientName ?? r.name),
      industry: opt(r.industry ?? r.classification),
    }));
  }

  async projects(): Promise<WmjProject[]> {
    const rows = await this.getAll<Record<string, unknown>>("projects");
    return rows.map((r) => ({
      projectKey: str(r.projectKey ?? r.projectID ?? r.id),
      projectNumber: opt(r.projectNumber),
      projectName: str(r.projectName),
      description: opt(r.description),
      status: opt(r.projectStatus ?? r.status),
      startDate: opt(r.startDate),
      endDate: opt(r.endDate ?? r.completionDate),
      clientKey: opt(r.clientKey ?? r.companyKey),
      clientName: opt(r.clientName ?? r.companyName),
      services: splitList(r.projectType ?? r.services),
    }));
  }

  /**
   * Assignments are derived from timesheet lines (who actually worked on what),
   * which is a far better signal of project history than task assignment alone.
   */
  async assignments(): Promise<WmjAssignment[]> {
    const rows = await this.getAll<Record<string, unknown>>("timesheets", { status: "approved" });
    const byKey = new Map<string, WmjAssignment>();
    for (const r of rows) {
      const projectKey = str(r.projectKey ?? r.projectID);
      const userKey = str(r.userKey ?? r.userID);
      if (!projectKey || !userKey) continue;
      const k = `${projectKey}:${userKey}`;
      const hours = Number(r.actualHours ?? r.hours ?? 0) || 0;
      const date = opt(r.workDate ?? r.date);
      const role = opt(r.serviceName ?? r.taskName ?? r.role);
      const cur = byKey.get(k);
      if (cur) {
        cur.hours += hours;
        if (date && (!cur.firstDate || date < cur.firstDate)) cur.firstDate = date;
        if (date && (!cur.lastDate || date > cur.lastDate)) cur.lastDate = date;
        cur.role = cur.role ?? role;
      } else {
        byKey.set(k, { projectKey, userKey, role, hours, firstDate: date, lastDate: date });
      }
    }
    return [...byKey.values()];
  }
}

const str = (v: unknown) => (v == null ? "" : String(v));
const opt = (v: unknown) => (v == null || v === "" ? undefined : String(v));
const splitList = (v: unknown): string[] | undefined => {
  if (Array.isArray(v)) return v.map(String);
  if (typeof v === "string" && v.trim()) return v.split(/[,;|]/).map((s) => s.trim()).filter(Boolean);
  return undefined;
};
