// Trade AI TypeScript SDK
// ------------------------------------------------------------------
// Zero dependencies. Works in Node 18+, Bun, Deno, browsers, and edge
// runtimes. Drop this file into your project, or 'pnpm add tradeai-sdk'
// once published.
//
// Quick start:
//
//   import { TradeAI } from "./tradeai";
//   const client = new TradeAI({ apiKey: process.env.TRADEAI_KEY! });
//   const me = await client.platforms.me();
//   const user = await client.users.create({
//     external_user_id: "u_42",
//     first_name: "Ada",
//     last_name: "Lovelace",
//   });

export type AssetClass = "equities" | "crypto" | "forex" | "commodities";
export type Timeframe = "1Min" | "5Min" | "15Min" | "1Hour" | "1Day";
export type UserType = "user" | "agent" | "ib";

export interface PlatformUser {
  id: string;
  external_user_id: string;
  first_name: string;
  last_name: string;
  mobile: string | null;
  email: string | null;
  country: string | null;
  trading_platform: string | null;
  type: UserType;
  status: "active" | "archived";
  created_at: string;
}

export interface Holding {
  symbol: string;
  asset_class: AssetClass;
  quantity: number;
  cost_basis?: number;
}

export interface TradeAIOptions {
  apiKey: string;
  baseUrl?: string;
  fetch?: typeof fetch;
}

export class TradeAIError extends Error {
  status: number;
  body: unknown;
  constructor(status: number, body: unknown, message: string) {
    super(message);
    this.status = status;
    this.body = body;
  }
}

export class TradeAI {
  private readonly apiKey: string;
  private readonly baseUrl: string;
  private readonly _fetch: typeof fetch;

  constructor(opts: TradeAIOptions) {
    if (!opts.apiKey) throw new Error("TradeAI: apiKey is required");
    this.apiKey = opts.apiKey;
    this.baseUrl = (opts.baseUrl ?? "https://tradeai.smartchain.consulting/api/public/v1").replace(/\/$/, "");
    this._fetch = opts.fetch ?? fetch;
  }

  private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
    const res = await this._fetch(this.baseUrl + path, {
      method,
      headers: {
        Authorization: `Bearer ${this.apiKey}`,
        "Content-Type": "application/json",
      },
      body: body == null ? undefined : JSON.stringify(body),
    });
    const text = await res.text();
    const parsed = text ? safeJson(text) : null;
    if (!res.ok) {
      const msg = (parsed && typeof parsed === "object" && "error" in (parsed as object))
        ? String((parsed as { error: unknown }).error)
        : `HTTP ${res.status}`;
      throw new TradeAIError(res.status, parsed, msg);
    }
    return parsed as T;
  }

  // ── Meta ──────────────────────────────────────────────
  health = () => this.request<{ ok: true }>("GET", "/health");
  usage = () => this.request<{ used: number; limit: number; remaining: number; reset_at: number }>("GET", "/usage");

  // ── Platforms ─────────────────────────────────────────
  platforms = {
    me: () => this.request<unknown>("GET", "/platforms/me"),
    update: (patch: { company_name?: string; contact_email?: string; country?: string; activity_other?: string | null }) =>
      this.request<unknown>("PATCH", "/platforms/me", patch),
  };

  // ── Users ─────────────────────────────────────────────
  users = {
    list: (q: { limit?: number; offset?: number; type?: UserType; email?: string; mobile?: string } = {}) => {
      const sp = new URLSearchParams();
      for (const [k, v] of Object.entries(q)) if (v !== undefined) sp.set(k, String(v));
      const qs = sp.toString();
      return this.request<{ users: PlatformUser[]; total: number }>("GET", "/users" + (qs ? "?" + qs : ""));
    },
    create: (input: {
      external_user_id: string;
      first_name: string;
      last_name: string;
      mobile?: string;
      email?: string;
      country?: string;
      trading_platform?: string;
      type?: UserType;
    }) => this.request<PlatformUser>("POST", "/users", input),
    get: (userId: string) => this.request<PlatformUser>("GET", `/users/${userId}`),
    update: (userId: string, patch: Partial<Omit<PlatformUser, "id" | "created_at" | "status">>) =>
      this.request<PlatformUser>("PATCH", `/users/${userId}`, patch),
    archive: (userId: string) => this.request<{ ok: true }>("DELETE", `/users/${userId}`),
  };

  // ── Per-user portfolio ────────────────────────────────
  portfolio = {
    latest: (userId: string) => this.request<unknown>("GET", `/users/${userId}/portfolio`),
    summary: (userId: string, holdings: Holding[]) =>
      this.request<unknown>("POST", `/users/${userId}/portfolio/summary`, { holdings }),
    insights: (userId: string, holdings: Holding[]) =>
      this.request<unknown>("POST", `/users/${userId}/portfolio/insights`, { holdings }),
  };

  // ── Per-user signals ──────────────────────────────────
  signals = {
    list: (userId: string, q: { asset_class?: AssetClass; symbol?: string; limit?: number } = {}) => {
      const sp = new URLSearchParams();
      for (const [k, v] of Object.entries(q)) if (v !== undefined) sp.set(k, String(v));
      const qs = sp.toString();
      return this.request<unknown>("GET", `/users/${userId}/signals` + (qs ? "?" + qs : ""));
    },
    generate: (userId: string, input: {
      asset_class: AssetClass;
      symbol?: string;
      symbols?: string[];
      timeframe?: Timeframe;
    }) => this.request<unknown>("POST", `/users/${userId}/signals/generate`, input),
  };

  // ── Per-user telegram ─────────────────────────────────
  // End-users never visit Trade AI. Your backend mints a code, shows
  // your own UI ("Open @YourBot and send /start CODE"), polls status,
  // then sends messages — all through this API.
  telegram = {
    /** Mint a one-time code (expires in 15 minutes). */
    linkCode: (userId: string) =>
      this.request<{ link_code: string; expires_at: string }>("POST", `/users/${userId}/telegram/link-code`),
    /** Get linking status, current code, and your child bot username. */
    status: (userId: string) =>
      this.request<{
        user_id: string;
        linked: boolean;
        telegram_username: string | null;
        telegram_chat_id: number | null;
        link_code: string | null;
        link_code_expires_at: string | null;
        linked_at: string | null;
        bot_username: string | null;
      }>("GET", `/users/${userId}/telegram`),
    /** Unlink the user's Telegram account and clear any pending code. */
    unlink: (userId: string) =>
      this.request<{ ok: true }>("DELETE", `/users/${userId}/telegram`),
    /** Push a message — only succeeds once the user has linked. */
    send: (userId: string, text: string) =>
      this.request<{ ok: true }>("POST", `/users/${userId}/telegram/send`, { text }),
  };
}

function safeJson(text: string): unknown {
  try { return JSON.parse(text); } catch { return text; }
}
