import axios, { AxiosInstance } from 'axios';
import * as os from 'os';
import * as fs from 'fs';
import { EventQueue } from './queue';
import { AgentConfig, getOrCreateDeviceId, getOsPlatform, getAgentVersion, PRICING_PATH } from './config';
import { ParsedEvent, PricingTable } from './parsers/types';

const RETRY_BASE_MS = 2000;
const RETRY_MAX_MS = 60000;
const RATE_LIMIT_BACKOFF_MS = 60000;

export class Uploader {
  private http: AxiosInstance;
  private deviceId: string;
  private osPlatform: string;
  private agentVersion: string;
  private hostname: string;
  private osUser: string;
  private currentRetryMs = RETRY_BASE_MS;
  private lastPricingVersion = '';

  constructor(
    private readonly token: string,
    private readonly config: AgentConfig,
    private readonly queue: EventQueue,
  ) {
    this.http = axios.create({
      baseURL: config.apiBase,
      timeout: 15000,
      headers: { 'Content-Type': 'application/json' },
    });
    this.deviceId = getOrCreateDeviceId();
    this.osPlatform = getOsPlatform();
    this.agentVersion = getAgentVersion();
    this.hostname = os.hostname();
    this.osUser = os.userInfo().username;
  }

  async flushOnce(): Promise<{ accepted: number; deduped: number; dlq: number } | null> {
    const { ids, events } = this.queue.peek(this.config.batchSize);
    if (events.length === 0) return null;
    try {
      const resp = await this.http.post(
        '/api/v1/ai-usage/events',
        { events: this.toApiShape(events) },
        {
          headers: {
            Authorization: `Bearer ${this.token}`,
            'X-Device-Id': this.deviceId,
            'X-Hostname': this.hostname,
            'X-Os-User': this.osUser,
            'X-Os-Platform': this.osPlatform,
            'X-Agent-Version': this.agentVersion,
          },
        },
      );
      this.currentRetryMs = RETRY_BASE_MS;
      const v = resp.headers['x-pricing-version'] || resp.headers['X-Pricing-Version'];
      if (v && v !== this.lastPricingVersion) {
        this.lastPricingVersion = v;
        await this.refreshPricing().catch(() => {});
      }
      this.queue.ack(ids);
      return resp.data?.data ?? null;
    } catch (err: any) {
      const status = err?.response?.status;
      if (status === 401) {
        console.error('[ffctk] 401 INVALID_TOKEN — please re-run `ffctk login <token>`');
        // hold off until restart
        throw new Error('AUTH_FAILED');
      }
      if (status === 403) {
        console.error('[ffctk] 403 DEVICE_BLOCKED — pausing this device');
        throw new Error('DEVICE_BLOCKED');
      }
      if (status === 413) {
        // shrink batch and let next flush succeed
        console.warn('[ffctk] 413 batch too large; will reduce next batch');
        return null;
      }
      if (status === 422) {
        // bad payload — drop these events to avoid loop
        this.queue.ack(ids);
        return null;
      }
      if (status === 429) {
        console.warn('[ffctk] 429 rate limit, backing off 60s');
        await sleep(RATE_LIMIT_BACKOFF_MS);
        return null;
      }
      // network / 5xx → exponential backoff
      console.warn(`[ffctk] flush failed (${status ?? 'network'}), retrying after ${this.currentRetryMs}ms`);
      await sleep(this.currentRetryMs);
      this.currentRetryMs = Math.min(this.currentRetryMs * 2, RETRY_MAX_MS);
      return null;
    }
  }

  async refreshPricing(): Promise<PricingTable | null> {
    try {
      const resp = await this.http.get('/api/v1/ai-usage/pricing');
      const data = resp.data?.data ?? resp.data;
      fs.writeFileSync(PRICING_PATH, JSON.stringify(data), { mode: 0o600 });
      return data;
    } catch {
      return null;
    }
  }

  private toApiShape(events: ParsedEvent[]) {
    return events.map((e) => ({
      rawMessageId: e.rawMessageId,
      tool: e.tool,
      sessionId: e.sessionId,
      projectPath: e.projectPath,
      model: e.model,
      ts: e.ts,
      inputTokens: e.inputTokens,
      outputTokens: e.outputTokens,
      cacheCreationTokens: e.cacheCreationTokens,
      cacheReadTokens: e.cacheReadTokens,
      estimatedCostUsd: e.estimatedCostUsd,
      // 富 metadata（全部可空；服务端按 schema 兜底处理）
      gitBranch: e.gitBranch,
      agentVersionEvent: e.agentVersionEvent,
      worktreeLabel: e.worktreeLabel,
      cwdBasename: e.cwdBasename,
      turnIndex: e.turnIndex,
      toolUseCount: e.toolUseCount,
      toolNames: e.toolNames,
      stopReason: e.stopReason,
      serviceTier: e.serviceTier,
    }));
  }
}

function sleep(ms: number) {
  return new Promise((r) => setTimeout(r, ms));
}
