/**
 * Cloud Platform API Client for visuallogic.ai
 *
 * Handles authentication, workspace file sync, compilation,
 * and project management against the VL cloud platform.
 */

const BASE_URL = 'https://www.visuallogic.ai';
const EDITOR_URL = 'https://editor.visuallogic.ai';
import { PARSEVL_URL } from '../data/versions.js';

export function isCloudUserAuthenticated(user) {
  if (!user || typeof user !== 'object') return false;
  const httpStatus = typeof user._status === 'number' ? user._status : 200;
  const businessCode = typeof user.code === 'number' ? user.code : null;
  const detail = String(user.detail || user.message || '').toLowerCase();

  if (httpStatus >= 300) return false;
  if (businessCode === 203) return false;
  if (businessCode !== null && businessCode >= 400) return false;
  if (!user.id && !user.name) return false;
  if (detail.includes('please login')) return false;

  return true;
}

export function sanitizeCloudUser(user) {
  if (!user || typeof user !== 'object') return null;

  const safeUser = {
    id: user.id,
    name: user.name,
    nickName: user.nickName,
    realName: user.realName,
    companyName: user.companyName,
    picture: user.picture,
    domain: user.domain,
    userType: user.userType,
    worksCount: user.worksCount,
    isPro: user.isPro,
    status: user.status,
  };

  return Object.fromEntries(
    Object.entries(safeUser).filter(([, value]) => value !== undefined && value !== null)
  );
}

export class CloudAPI {
  constructor(config) {
    this.config = config;
    this._userInfo = null;
    this._lastValidated = 0;
  }

  // ─── Helpers ──────────────────────────────────────────────

  _cookie() {
    return this.config.cookie || '';
  }

  _headers(extra = {}) {
    return {
      'Content-Type': 'application/json',
      'Cookie': `ih5bearer=${this._cookie()}`,
      ...extra,
    };
  }

  async _post(path, body = {}, { base = BASE_URL } = {}) {
    const url = `${base}${path}`;
    const res = await fetch(url, {
      method: 'POST',
      headers: this._headers(),
      body: JSON.stringify(body),
    });
    // Capture set-cookie if present (for login)
    const setCookie = res.headers.get('set-cookie') || '';
    const json = await res.json();
    return { ...json, _setCookie: setCookie, _status: res.status };
  }

  // ─── Auth ─────────────────────────────────────────────────

  /** Login with enterprise account. Returns token + sets cookie. */
  async login(username, password, companyName) {
    const res = await this._post('/ih5/app/user/login', { username, password, companyName });
    if (res.token) {
      this.config.cookie = res.token;
      this._userInfo = null;
      this._lastValidated = 0;
      const user = await this.getUserInfo(true);
      return { ok: true, cookie: res.token, user };
    }
    return { ok: false, error: res.reason || res.message || 'Login failed', code: res.code };
  }

  /** Get current user info. Also serves as session validation. */
  async getUserInfo(forceRefresh = false) {
    if (this._userInfo && !forceRefresh && (Date.now() - this._lastValidated < 5 * 60 * 1000)) {
      return this._userInfo;
    }
    if (!this._cookie()) return null;
    try {
      const res = await this._post('/ih5/app/user/userinfo');
      if (isCloudUserAuthenticated(res)) {
        this._userInfo = sanitizeCloudUser(res);
        this._lastValidated = Date.now();
        return this._userInfo;
      }
      this._userInfo = null;
      this._lastValidated = 0;
      return null;
    } catch {
      this._userInfo = null;
      return null;
    }
  }

  /**
   * Login via Google OAuth:
   * 1. Call googleLoginOrRegister to register/find user → returns gid
   * 2. Call thirdLogin with gid → returns ih5bearer session cookie
   */
  async googleLoginOrRegister({ email, name, id, picture }) {
    // Step 1: Register/find user via Google info
    const res = await this._post('/api/12025157/googleLoginOrRegister', { email, name, id, picture });
    const gid = res.gid || res.data?.gid || id;

    // Step 2: Call thirdLogin to get ih5bearer session
    const thirdLoginUrl = `${BASE_URL}/ih5/app/user/thirdLogin?code=${encodeURIComponent(gid)}&idKey=gid&iconKey=picture&nickNameKey=name&name=google`;
    const thirdRes = await fetch(thirdLoginUrl, { redirect: 'manual' });
    const setCookie = thirdRes.headers.get('set-cookie') || '';
    const match = setCookie.match(/ih5bearer=([^;]+)/);
    if (match) {
      this.config.cookie = match[1];
      this._userInfo = null;
      this._lastValidated = 0;
      const user = await this.getUserInfo(true);
      return { ok: true, cookie: match[1], user };
    }

    return { ok: false, error: 'Google login succeeded but could not obtain session. Try Token tab.' };
  }

  /** Check if current cookie/session is valid */
  async validateSession() {
    const info = await this.getUserInfo(true);
    return !!info;
  }

  // ─── Projects & Apps ──────────────────────────────────────

  /** List user's apps/work groups */
  async listApps({ offset = 0, limit = 50 } = {}) {
    const res = await this._post('/ih5/app/apps/list', { offset, limit, asc: false });
    return res.list || [];
  }

  /** Get work group detail (gid → nids, works, title) */
  async getWorkGroup(gid) {
    const res = await this._post('/ih5/editor/workGroup/get', { gid: Number(gid) });
    return res;
  }

  /** Create a new project in the AI editor */
  async createProject() {
    const res = await this._post('/edt/aiChat/createProject/v3');
    return res.data || res;
  }

  /** Get work config (domain, preview URLs, stage settings) */
  async getWorkConfig(nid) {
    const res = await this._post('/ih5/editor/work/getConfig', { type: 'settings', nid: Number(nid) });
    return res;
  }

  // ─── Workspace File Operations (gid-level) ───────────────

  /** List files in a workspace */
  async listFiles(gid) {
    const res = await this._post('/ih5/editor/workspace/listFile', { gid: Number(gid) });
    return res.files || [];
  }

  /** Read a single file from workspace */
  async readFile(gid, filePath, version) {
    const body = { gid: Number(gid), path: filePath };
    if (version !== undefined) body.version = version;
    return await this._post('/ih5/editor/workspace/readFile', body);
  }

  /** Read multiple files from workspace */
  async readFiles(gid, paths) {
    const res = await this._post('/ih5/editor/workspace/readFiles', {
      gid: Number(gid),
      paths,
    });
    return res.files || [];
  }

  /** Write a single file to workspace */
  async writeFile(gid, filePath, content) {
    return await this._post('/ih5/editor/workspace/writeFile', {
      gid: Number(gid),
      path: filePath,
      content,
    });
  }

  /** Write multiple files to workspace (batch) */
  async writeFiles(gid, files) {
    return await this._post('/ih5/editor/workspace/writeFiles', {
      gid: Number(gid),
      files, // [{path, content}, ...]
    });
  }

  /** Create file(s) in workspace */
  async createFiles(gid, paths) {
    return await this._post('/ih5/editor/workspace/createFiles', {
      gid: Number(gid),
      paths,
    });
  }

  /** Delete file(s) from workspace */
  async deleteFiles(gid, paths) {
    return await this._post('/ih5/editor/workspace/deleteFiles', {
      gid: Number(gid),
      paths,
    });
  }

  // ─── Compile (parsevl) ────────────────────────────────────

  /**
   * Compile project using workspace mode (targetGid).
   * Requires files to be synced to the cloud workspace first.
   */
  async compileWorkspace(targetGid, projectName) {
    const body = { action: 'parsePjt', targetGid: Number(targetGid) };
    if (projectName) body.projectName = projectName;
    return await this._post('/edtfn/parsevl', body, { base: PARSEVL_URL });
  }

  /**
   * Lint project using workspace mode (targetGid).
   * Static validation only — no preview/case generation, faster than compile.
   */
  async lintWorkspace(targetGid) {
    return await this._post('/edtfn/parsevl', {
      action: 'lintPjt',
      targetGid: Number(targetGid),
    }, { base: PARSEVL_URL });
  }

  /**
   * Compile project using ZIP upload mode (file as base64).
   * No workspace dependency — sends all files in a ZIP.
   */
  async compileZip(fileBase64, projectName, targetGid) {
    const body = { action: 'parsePjt', file: fileBase64 };
    if (projectName) body.projectName = projectName;
    if (targetGid) body.targetGid = Number(targetGid);
    return await this._post('/edtfn/parsevl', body, { base: PARSEVL_URL });
  }

  // ─── Sync: Local ↔ Cloud Workspace ───────────────────────

  /**
   * Push local files to cloud workspace.
   * @param {string} gid - Workspace group ID
   * @param {Array<{path: string, content: string}>} localFiles - Files to push
   * @returns {{ created: string[], updated: string[], unchanged: string[] }}
   */
  async syncPush(gid, localFiles) {
    // 1. List existing cloud files
    const cloudFiles = await this.listFiles(gid);
    const cloudPaths = new Set(cloudFiles.map(f => f.path));

    // 2. Separate into create vs write
    const toCreate = [];
    const toWrite = [];

    for (const f of localFiles) {
      if (cloudPaths.has(f.path)) {
        toWrite.push(f);
      } else {
        toCreate.push(f.path);
        toWrite.push(f);
      }
    }

    // 3. Create new paths first
    if (toCreate.length > 0) {
      await this.createFiles(gid, toCreate);
    }

    // 4. Write all files in batch
    if (toWrite.length > 0) {
      // Batch in chunks of 20 to avoid request size limits
      for (let i = 0; i < toWrite.length; i += 20) {
        const chunk = toWrite.slice(i, i + 20);
        await this.writeFiles(gid, chunk);
      }
    }

    return { created: toCreate, updated: toWrite.map(f => f.path), total: toWrite.length };
  }

  /**
   * Pull cloud workspace files to get content.
   * @param {string} gid
   * @returns {Array<{path: string, content: string}>}
   */
  async syncPull(gid) {
    const cloudFiles = await this.listFiles(gid);
    if (!cloudFiles.length) return [];

    const paths = cloudFiles.filter(f => f.version > 0).map(f => f.path);
    if (!paths.length) return [];

    // Read in batches of 20
    const allFiles = [];
    for (let i = 0; i < paths.length; i += 20) {
      const chunk = paths.slice(i, i + 20);
      const files = await this.readFiles(gid, chunk);
      allFiles.push(...files);
    }
    return allFiles;
  }

  /**
   * Full sync-and-compile workflow:
   * 1. Push local files to cloud workspace
   * 2. Compile using workspace mode (targetGid)
   * 3. Return compile result with preview URLs
   */
  async syncAndCompile(gid, localFiles, projectName, onProgress) {
    onProgress?.('syncing', `Syncing ${localFiles.length} files to cloud...`);
    const syncResult = await this.syncPush(gid, localFiles);

    onProgress?.('compiling', `Compiling via workspace (GID: ${gid})...`);
    const compileResult = await this.compileWorkspace(gid, projectName);

    return {
      sync: syncResult,
      compile: compileResult,
    };
  }

  // ─── Publish ──────────────────────────────────────────────

  /** Publish/unpublish a version */
  async launchVersion(nid, version) {
    return await this._post('/ih5/editor/work/launchVersion', {
      nid: Number(nid),
      version: String(version),
    });
  }

  // ─── Billing ──────────────────────────────────────────────

  async queryBalance() {
    const res = await this._post('/ih5/app/pay/queryRemain');
    return res.balance;
  }
}
