/** AgentForms API client — thin wrapper over /api/v2/. */

import type {
  ApiKey,
  FieldDefinition,
  Form,
  FormsResponse,
  KeysResponse,
  Permission,
  Submission,
  SubmissionsResponse,
} from './types';

// ---------------------------------------------------------------------------
// Error class
// ---------------------------------------------------------------------------

export class AgentFormsError extends Error {
  public readonly statusCode: number;
  public readonly response: unknown;

  constructor(message: string, statusCode: number = 0, response: unknown = null) {
    super(message);
    this.name = 'AgentFormsError';
    this.statusCode = statusCode;
    this.response = response;
  }
}

// ---------------------------------------------------------------------------
// Base client internals
// ---------------------------------------------------------------------------

interface ClientInternals {
  readonly apiKey: string;
  readonly baseURL: string;
  readonly timeout: number;
}

/**
 * Make an HTTP request and parse the JSON response.
 * Throws AgentFormsError on status >= 400.
 */
async function request(
  internals: ClientInternals,
  method: string,
  path: string,
  options?: {
    params?: Record<string, string | number>;
    body?: Record<string, unknown>;
  },
): Promise<unknown> {
  const url = new URL(`${internals.baseURL}/${path.replace(/^\//, '')}`);

  if (options?.params) {
    for (const [key, value] of Object.entries(options.params)) {
      url.searchParams.append(key, String(value));
    }
  }

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), internals.timeout * 1000);

  try {
    const headers: Record<string, string> = {
      Authorization: `Bearer ${internals.apiKey}`,
      Accept: 'application/json',
    };

    const fetchOptions: RequestInit = {
      method,
      headers,
      signal: controller.signal,
    };

    if (options?.body) {
      headers['Content-Type'] = 'application/json';
      fetchOptions.body = JSON.stringify(options.body);
    }

    const resp = await fetch(url.toString(), fetchOptions);

    if (resp.status >= 400) {
      let message: string;
      let body: unknown;
      try {
        body = await resp.json();
        message = (body as Record<string, unknown>).error as string ?? resp.statusText;
      } catch {
        body = await resp.text();
        message = body as string;
      }
      throw new AgentFormsError(message, resp.status, body);
    }

    if (resp.status === 204) {
      return {};
    }

    return resp.json() as Promise<unknown>;
  } finally {
    clearTimeout(timer);
  }
}

/**
 * Enrich a Form object with computed URL properties.
 */
function enrichForm(raw: Form): Form {
  return {
    ...raw,
    public_url: `https://agentforms.io/f/${raw.token}`,
    share_url: `https://agentforms.io/${raw.token}`,
  };
}

/**
 * Enrich a list of Form objects.
 */
function enrichFormsResponse(raw: FormsResponse): FormsResponse {
  return {
    ...raw,
    forms: (raw.forms ?? []).map(enrichForm),
  };
}

// ---------------------------------------------------------------------------
// FormsClient
// ---------------------------------------------------------------------------

export class FormsClient {
  private readonly _c: ClientInternals;

  constructor(client: ClientInternals) {
    this._c = client;
  }

  /** List all forms. */
  async list(limit: number = 50, offset: number = 0): Promise<FormsResponse> {
    const result = await request(this._c, 'GET', '/forms', {
      params: { limit, offset },
    });
    return enrichFormsResponse(result as FormsResponse);
  }

  /**
   * Create a new form programmatically.
   *
   * @param name - Form display name
   * @param fields - List of field definitions
   * @param metadata - Optional JSON metadata attached to the form
   */
  async create(
    name: string,
    fields: FieldDefinition[],
    metadata?: Record<string, unknown>,
  ): Promise<Form> {
    const body: Record<string, unknown> = { name, fields };
    if (metadata) {
      body.metadata = metadata;
    }
    const result = await request(this._c, 'POST', '/forms', { body });
    return enrichForm(result as Form);
  }

  /**
   * Generate a form from natural language using AI.
   * Tier-gated (Starter+).
   *
   * @param prompt - Natural language description (e.g., "A survey for event feedback")
   * @param formName - Override the AI-generated form name
   */
  async generate(prompt: string, formName?: string): Promise<Form> {
    const body: Record<string, unknown> = { prompt };
    if (formName) {
      body.form_name = formName;
    }
    const result = await request(this._c, 'POST', '/forms/generate', { body });
    return enrichForm(result as Form);
  }

  /** Get form details by token. */
  async get(token: string): Promise<Form> {
    const result = await request(this._c, 'GET', `/forms/${token}`);
    return enrichForm(result as Form);
  }

  /** Replace all fields on a form. */
  async updateFields(token: string, fields: FieldDefinition[]): Promise<Record<string, unknown>> {
    return request(
      this._c,
      'PUT',
      `/forms/${token}/fields`,
      { body: { fields } },
    ) as Promise<Record<string, unknown>>;
  }

  /** Delete a form and all submissions. */
  async delete(token: string): Promise<Record<string, unknown>> {
    return request(this._c, 'DELETE', `/forms/${token}`) as Promise<Record<string, unknown>>;
  }

  /** Get public form config. */
  async config(token: string): Promise<Record<string, unknown>> {
    return request(this._c, 'GET', `/forms/${token}/config`) as Promise<Record<string, unknown>>;
  }
}

// ---------------------------------------------------------------------------
// KeysClient
// ---------------------------------------------------------------------------

export class KeysClient {
  private readonly _c: ClientInternals;

  constructor(client: ClientInternals) {
    this._c = client;
  }

  /** List all API keys. */
  async list(): Promise<ApiKey[]> {
    const result = (await request(this._c, 'GET', '/keys')) as KeysResponse;
    return result.keys ?? [];
  }

  /**
   * Create a new API key.
   *
   * @param name - Human-readable name
   * @param permissions - List of permissions (defaults to all if omitted)
   * @returns Key details including the full_key (returned once — store it securely)
   */
  async create(name: string, permissions?: Permission[]): Promise<Record<string, unknown>> {
    const body: Record<string, unknown> = { name };
    if (permissions) {
      const permObj: Record<string, boolean> = {};
      for (const p of permissions) {
        permObj[p] = true;
      }
      body.permissions = permObj;
    }
    return request(this._c, 'POST', '/keys', {
      body,
    }) as Promise<Record<string, unknown>>;
  }

  /** Revoke an API key. */
  async revoke(keyId: number): Promise<Record<string, unknown>> {
    return request(this._c, 'DELETE', `/keys/${keyId}`) as Promise<Record<string, unknown>>;
  }
}

// ---------------------------------------------------------------------------
// SubmissionsClient
// ---------------------------------------------------------------------------

export class SubmissionsClient {
  private readonly _c: ClientInternals;

  constructor(client: ClientInternals) {
    this._c = client;
  }

  /** List submissions for a form. */
  async list(
    formToken: string,
    limit: number = 50,
    offset: number = 0,
  ): Promise<SubmissionsResponse> {
    return request(
      this._c,
      'GET',
      `/forms/${formToken}/submissions`,
      { params: { limit, offset } },
    ) as Promise<SubmissionsResponse>;
  }

  /** Get a single submission. */
  async get(formToken: string, submissionId: number): Promise<Submission> {
    return request(
      this._c,
      'GET',
      `/forms/${formToken}/submissions/${submissionId}`,
    ) as Promise<Submission>;
  }
}

// ---------------------------------------------------------------------------
// Main client
// ---------------------------------------------------------------------------

/**
 * Client for the AgentForms Agent API.
 *
 * @example
 * ```ts
 * import { AgentForms } from '@agentforms/sdk';
 *
 * const af = new AgentForms('afk_live_...');
 *
 * // Create a form
 * const form = await af.forms.create('Customer Feedback', [
 *   { name: 'rating', label: 'Rating', type: 'select', options: ['1','2','3','4','5'], required: true },
 *   { name: 'feedback', label: 'Feedback', type: 'textarea' },
 * ]);
 * console.log(form.share_url);
 * ```
 */
export class AgentForms {
  public readonly forms: FormsClient;
  public readonly keys: KeysClient;
  public readonly submissions: SubmissionsClient;

  /**
   * @param apiKey - Your AgentForms API key
   * @param baseURL - API base URL (default: https://agentforms.io/api/v2)
   * @param timeout - Request timeout in seconds (default: 30)
   */
  constructor(
    public readonly apiKey: string,
    public readonly baseURL: string = 'https://agentforms.io/api/v2',
    public readonly timeout: number = 30,
  ) {
    const internals: ClientInternals = {
      apiKey,
      baseURL: baseURL.replace(/\/+$/, ''),
      timeout,
    };

    this.forms = new FormsClient(internals);
    this.keys = new KeysClient(internals);
    this.submissions = new SubmissionsClient(internals);
  }
}