import { chromium, BrowserContext, Page } from 'playwright';
import jsQR from 'jsqr';
import { getDb } from '../database/database';
import { escapeLike } from '../utils/time';

interface QrSession {
  id: string;
  browserContext: BrowserContext;
  page: Page;
  createdAt: number;
  cookieSnapshot: string;
  lastPollAt: number;
  qrUrl: string;
  status: 'pending' | 'scanned' | 'logged_in' | 'expired' | 'error';
  error?: string;
}

const SESSIONS = new Map<string, QrSession>();
const SESSION_TTL = 5 * 60 * 1000; // 5 minutes
const COOKIE_CHECK_INTERVAL = 1500; // 1.5s between cookie checks

const CHROMIUM_PATH = process.env.CHROMIUM_PATH || '/usr/bin/chromium-browser';

// Clean up old sessions periodically
setInterval(() => {
  const now = Date.now();
  for (const [id, session] of SESSIONS.entries()) {
    if (now - session.createdAt > SESSION_TTL) {
      cleanupSession(id);
    }
  }
}, 60000);

function cleanupSession(id: string) {
  const session = SESSIONS.get(id);
  if (session) {
    try {
      session.browserContext.close().catch(() => {});
    } catch {}
    SESSIONS.delete(id);
  }
}

/**
 * Extract QR code URL from the login page canvas using jsQR.
 * The actual login QR code is Canvas #0 (anonymous, 177x177), NOT #react-qrcode-logo.
 */
async function extractQrUrl(page: Page): Promise<string> {
  // Run inside Playwright's browser context (as a string to avoid Node TS type errors)
  const raw = await page.evaluate(`(() => {
    const canvases = document.querySelectorAll('canvas');
    var results = [];
    for (var i = 0; i < canvases.length; i++) {
      try {
        var c = canvases[i];
        var ctx = c.getContext('2d');
        if (!ctx) continue;
        var imageData = ctx.getImageData(0, 0, c.width, c.height);
        results.push({
          index: i,
          w: c.width,
          h: c.height,
          data: Array.from(imageData.data)
        });
      } catch(e) {}
    }
    return results;
  })()`) as unknown as { index: number; w: number; h: number; data: number[] }[];

  if (!raw || raw.length === 0) {
    throw new Error('页面没有可用的 canvas');
  }

  // Try to decode each canvas, preferring the one with su.quark.cn URL
  let bestUrl = '';
  let bestResult: { index: number; w: number; h: number; data: number[] } | null = null;

  for (const canvas of raw) {
    const code = jsQR(new Uint8ClampedArray(canvas.data), canvas.w, canvas.h);
    if (code && code.data) {
      // If this is the login QR code (has su.quark.cn), use it immediately
      if (code.data.includes('su.quark.cn')) {
        return code.data;
      }
      // Otherwise keep it as fallback
      if (!bestUrl) {
        bestUrl = code.data;
        bestResult = canvas;
      }
    }
  }

  if (bestUrl) {
    return bestUrl;
  }

  throw new Error('无法解析二维码内容');
}

/**
 * Start a QR code login session.
 * Launches headless Chromium, navigates to Quark login page, extracts QR code URL.
 */
export async function startQrLogin(): Promise<{
  sessionId: string;
  qrUrl: string;
  expiresIn: number;
}> {
  // Clean up any existing expired sessions
  for (const [id, session] of SESSIONS.entries()) {
    if (Date.now() - session.createdAt > SESSION_TTL) {
      cleanupSession(id);
    }
  }

  const browser = await chromium.launch({
    executablePath: CHROMIUM_PATH,
    headless: true,
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage',
      '--disable-gpu',
      '--no-first-run',
      '--no-zygote',
    ],
  });

  const browserContext = await browser.newContext({
    userAgent:
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    viewport: { width: 1280, height: 800 },
    locale: 'zh-CN',
  });

  const page = await browserContext.newPage();
  const sessionId = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);

  try {
    // Navigate to Quark login page (now the homepage itself has QR login)
    await page.goto('https://pan.quark.cn/', {
      waitUntil: 'commit',
      timeout: 30000,
    });

    // Wait for the QR code canvas to appear
    await page.waitForSelector('canvas', { timeout: 15000 });

    // Extra wait for the QR code to fully render
    await page.waitForTimeout(2000);

    // Extract the QR code URL from the canvas
    const qrUrl = await extractQrUrl(page);

    // Take initial cookie snapshot
    const cookies = await browserContext.cookies();
    const cookieSnapshot = cookies.map(c => `${c.name}=${c.value}`).join('; ');

    const session: QrSession = {
      id: sessionId,
      browserContext,
      page,
      createdAt: Date.now(),
      cookieSnapshot,
      lastPollAt: Date.now(),
      qrUrl,
      status: 'pending',
    };

    SESSIONS.set(sessionId, session);

    // Start background polling for login detection
    pollLoginStatus(session);

    // Handle page navigation (like redirect after login)
    page.on('framenavigated', async (frame) => {
      if (frame === page.mainFrame()) {
        const url = frame.url();
        if (url === 'about:blank') {
          await checkAndCaptureCookies(session);
        }
      }
    });

    // Handle popups/dialogs
    page.on('popup', async (popup) => {
      try {
        await popup.waitForLoadState('networkidle', { timeout: 10000 });
        await checkAndCaptureCookies(session);
      } catch {}
    });

    return {
      sessionId,
      qrUrl,
      expiresIn: SESSION_TTL / 1000,
    };
  } catch (err: any) {
    // Clean up on failure
    try { await browserContext.close(); } catch {}
    try { browser.close().catch(() => {}); } catch {}
    SESSIONS.delete(sessionId);
    throw new Error(`启动扫码登录失败: ${err.message}`);
  }
}

/**
 * Poll login status in background.
 * Checks cookies every COOKIE_CHECK_INTERVAL ms for new session tokens.
 */
async function pollLoginStatus(session: QrSession) {
  const checkInterval = setInterval(async () => {
    try {
      const now = Date.now();

      // Check if expired
      if (now - session.createdAt > SESSION_TTL) {
        clearInterval(checkInterval);
        session.status = 'expired';
        cleanupSession(session.id);
        return;
      }

      session.lastPollAt = now;

      // Check cookies
      const cookies = await session.browserContext.cookies();
      const cookieStr = cookies.map(c => `${c.name}=${c.value}`).join('; ');

      // Check for session cookies indicating login
      const hasSessionCookie = cookies.some(
        c => (c.name === '__st' || c.name === 'pus' || c.name === '__pus' || c.name === '__ktd')
      );

      if (hasSessionCookie) {
        session.cookieSnapshot = cookieStr;
        session.status = 'logged_in';
        clearInterval(checkInterval);
        return;
      }

      // Check URL change as alternative indicator
      const url = session.page.url();
      if (!url.includes('login') && !url.includes('qrcode') && url !== 'about:blank' && url !== 'https://pan.quark.cn/' && url.length > 10) {
        await checkAndCaptureCookies(session);
      }
    } catch (err: any) {
      // Page might have been closed
      clearInterval(checkInterval);
    }
  }, COOKIE_CHECK_INTERVAL);
}

/**
 * Check cookies after navigation/redirect and capture them if login succeeded.
 */
async function checkAndCaptureCookies(session: QrSession) {
  try {
    const cookies = await session.browserContext.cookies();
    const cookieStr = cookies.map(c => `${c.name}=${c.value}`).join('; ');
    const hasSessionCookie = cookies.some(
      c => (c.name === '__st' || c.name === 'pus' || c.name === '__pus' || c.name === '__ktd')
    );

    if (hasSessionCookie) {
      session.cookieSnapshot = cookieStr;
      session.status = 'logged_in';
    } else if (cookies.length > 3) {
      const newCookies = cookies.filter(
        c => !['ctoken', 'b-user-id', '__wpkreporterwid_'].includes(c.name)
      );
      if (newCookies.length > 0) {
        session.cookieSnapshot = cookieStr;
        try {
          const resp = await session.page.evaluate(async () => {
            const r = await fetch('https://pan.quark.cn/account/info', {
              credentials: 'include',
            });
            return await r.text();
          });
          const data = JSON.parse(resp);
          if (data?.data?.nickname) {
            session.status = 'logged_in';
          }
        } catch {}
      }
    }
  } catch {}
}

/**
 * Get the login status for a session.
 */
export async function getQrLoginStatus(sessionId: string): Promise<{
  status: string;
  cookie?: string;
  nickname?: string;
  storage_used?: string;
  storage_total?: string;
  autoUpdated?: boolean;
  updatedConfigId?: number;
}> {
  const session = SESSIONS.get(sessionId);
  if (!session) {
    return { status: 'expired' };
  }

  // Check if expired
  if (Date.now() - session.createdAt > SESSION_TTL) {
    session.status = 'expired';
    cleanupSession(sessionId);
    return { status: 'expired' };
  }

  if (session.status === 'logged_in') {
    // Try to get nickname too
    let nickname = '';
    try {
      const resp = await session.page.evaluate(async () => {
        const r = await fetch('https://pan.quark.cn/account/info', {
          credentials: 'include',
        });
        return await r.text();
      });
      const data = JSON.parse(resp);
      nickname = data?.data?.nickname || '';
    } catch {}

    // Fetch capacity info from within the browser context (has full JS signing)
    let storageTotal = '';
    let storageUsed = '';
    try {
      const capResp = await session.page.evaluate(async () => {
        const r = await fetch(
          'https://pan.quark.cn/1/clouddrive/capacity/detail?pr=ucpro&fr=pc',
          { credentials: 'include' }
        );
        return await r.text();
      });
      const capData = JSON.parse(capResp);
      if (capData.status === 200 && capData.data?.capacity_summary) {
        const summary = capData.data.capacity_summary;
        const total = summary.sum_capacity || 0;
        storageTotal = formatBytes(total);
        storageUsed = '0 B'; // capacity/detail doesn't return used_size
      }
    } catch {}

    // Build full cookie string including httpOnly cookies
    const cookies = await session.browserContext.cookies();
    const cookieStr = cookies.map(c => `${c.name}=${c.value}`).join('; ');

    // Extract __uid from cookie for duplicate detection
    const uidMatch = cookieStr.match(/(?<!\\w)__uid=([a-f0-9-]+)/);
    let autoUpdated = false;
    let updatedConfigId: number | undefined;

    if (uidMatch) {
      const uid = uidMatch[1];
      try {
        const db = getDb();
        const existing = db.prepare(
          `SELECT id, nickname FROM cloud_configs WHERE cloud_type = 'quark' AND cookie LIKE ?`
        ).get(`%${escapeLike(uid)}%`) as { id: number; nickname: string } | undefined;

        if (existing) {
          // Same account → auto-update cookie with capacity info too
          const localTimestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
          db.prepare(
            `UPDATE cloud_configs SET cookie = ?, storage_used = ?, storage_total = ?, updated_at = ? WHERE id = ?`
          ).run(cookieStr, storageUsed || null, storageTotal || null, localTimestamp, existing.id);
          autoUpdated = true;
          updatedConfigId = existing.id;
        }
      } catch {}
    }

    // Clean up session after successful login
    cleanupSession(sessionId);

    return {
      status: 'logged_in',
      cookie: cookieStr,
      nickname,
      storage_used: storageUsed,
      storage_total: storageTotal,
      autoUpdated,
      updatedConfigId,
    };
  }

  return { status: session.status };
}

function formatBytes(bytes: number): string {
  if (bytes === 0) return '0 B';
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
}

/**
 * Cancel a QR login session.
 */
export async function cancelQrLogin(sessionId: string): Promise<void> {
  cleanupSession(sessionId);
}
