// arcrun OAuth 登入路由
// GET  /auth/google/start           → redirect to Google OAuth
// GET  /auth/github/start           → redirect to GitHub OAuth
// GET  /auth/callback               → exchange code, create session
// POST /auth/logout                 → clear session cookie
// GET  /me                          → current user info
// PUT  /me/api-key/rotate           → generate new api key
// DELETE /me/api-key                → revoke api key

import { Hono } from 'hono';
import type { Bindings } from '../types';
import { storeCredential } from './credentials';

export const authRouter = new Hono<{ Bindings: Bindings }>();

// ─── Types ────────────────────────────────────────────────────────────────────

type UserRecord = {
  email: string;
  display_name: string;
  avatar_url?: string;
  api_key: string;
  provider: 'google' | 'github';
  provider_id: string;
  created_at: string;
  revoked?: boolean;
};

type SessionRecord = {
  user_key: string;   // "user:{provider}:{provider_id}"
  api_key: string;
  email: string;
  expires_at: number; // unix timestamp ms
};

type OAuthStateRecord = {
  provider: 'google' | 'github';
  redirect_back: string;
  created_at: number;
};

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

function getLandingOrigin(c: { req: { raw: Request } }): string {
  const origin = c.req.raw.headers.get('origin');
  // 允許的 landing origins
  const allowed = ['https://arcrun.dev', 'https://www.arcrun.dev'];
  if (origin && allowed.includes(origin)) return origin;
  return 'https://arcrun.dev';
}

/**
 * 產生 API Key（隨機，與 /me/api-key/rotate 同一套）。
 *
 * key 與 email 無關、不可預測；登入走 USERS_KV 讀出的 api_key，不重算。
 */
function generateApiKey(): string {
  return 'ak_' + randomToken(24);
}

/** 幂等寫入 auth_recipe 到 RECIPES KV（若已存在相同版本則跳過） */
async function upsertAuthRecipe(recipes: KVNamespace, recipe: Record<string, unknown>): Promise<void> {
  const key = `auth_recipe:${recipe.service}`;
  const existing = await recipes.get(key);
  if (existing) return; // 已存在，不覆蓋（用戶可能已自訂）
  await recipes.put(key, JSON.stringify({ ...recipe, created_at: Date.now(), updated_at: Date.now() }));
}

/** 產生隨機 token（用於 session ID 和 state） */
function randomToken(bytes = 32): string {
  const arr = new Uint8Array(bytes);
  crypto.getRandomValues(arr);
  return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('');
}

/** 從 Cookie header 取 session ID */
function getSessionId(req: Request): string | null {
  const cookie = req.headers.get('cookie') ?? '';
  const match = cookie.match(/arcrun_session=([a-f0-9]+)/);
  return match ? match[1] : null;
}

/** 從 Request 取 API Key（X-Arcrun-API-Key header 或 Authorization: Bearer） */
function getApiKeyFromRequest(req: Request): string | null {
  const direct = req.headers.get('x-arcrun-api-key');
  if (direct) return direct;
  const auth = req.headers.get('authorization') ?? '';
  const match = auth.match(/^Bearer\s+(ak_\S+)/i);
  return match ? match[1] : null;
}

/** 驗證 session → 回傳 user record，或 null */
async function resolveSession(c: { req: { raw: Request }; env: Bindings }): Promise<UserRecord | null> {
  const sessId = getSessionId(c.req.raw);
  if (sessId) {
    const sess = await c.env.SESSIONS_KV.get<SessionRecord>(`sess:${sessId}`, 'json');
    if (sess && sess.expires_at > Date.now()) {
      const user = await c.env.USERS_KV.get<UserRecord>(sess.user_key, 'json');
      if (user && !user.revoked) return user;
    }
  }
  // Fallback: API Key header
  const apiKey = getApiKeyFromRequest(c.req.raw);
  if (apiKey) {
    // 掃描 USERS_KV by api_key 太慢；改用 reverse index: apikey:{ak_...} → user_key
    const userKey = await c.env.USERS_KV.get(`apikey:${apiKey}`);
    if (userKey) {
      const user = await c.env.USERS_KV.get<UserRecord>(userKey, 'json');
      if (user && !user.revoked && user.api_key === apiKey) return user;
    }
  }
  return null;
}

// ─── Google OAuth ─────────────────────────────────────────────────────────────

authRouter.get('/auth/google/start', async (c) => {
  const clientId = c.env.GOOGLE_CLIENT_ID;
  if (!clientId) return c.json({ error: 'Google OAuth not configured' }, 503);

  const state = randomToken(16);
  const stateRecord: OAuthStateRecord = {
    provider: 'google',
    redirect_back: c.req.query('redirect') ?? '/dashboard',
    created_at: Date.now(),
  };
  // state TTL = 10 minutes
  await c.env.SESSIONS_KV.put(`state:${state}`, JSON.stringify(stateRecord), { expirationTtl: 600 });

  const redirectUri = 'https://cypher.arcrun.dev/auth/callback';
  const params = new URLSearchParams({
    client_id: clientId,
    redirect_uri: redirectUri,
    response_type: 'code',
    scope: 'openid profile email',
    state,
    access_type: 'offline',
    prompt: 'consent',
  });

  return Response.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params}`, 302);
});

// ─── GitHub OAuth ─────────────────────────────────────────────────────────────

authRouter.get('/auth/github/start', async (c) => {
  const clientId = c.env.GITHUB_CLIENT_ID;
  if (!clientId) return c.json({ error: 'GitHub OAuth not configured' }, 503);

  const state = randomToken(16);
  const stateRecord: OAuthStateRecord = {
    provider: 'github',
    redirect_back: c.req.query('redirect') ?? '/dashboard',
    created_at: Date.now(),
  };
  await c.env.SESSIONS_KV.put(`state:${state}`, JSON.stringify(stateRecord), { expirationTtl: 600 });

  const redirectUri = 'https://cypher.arcrun.dev/auth/callback';
  const params = new URLSearchParams({
    client_id: clientId,
    redirect_uri: redirectUri,
    scope: 'read:user user:email',
    state,
  });

  return Response.redirect(`https://github.com/login/oauth/authorize?${params}`, 302);
});

// ─── OAuth Callback ───────────────────────────────────────────────────────────

authRouter.get('/auth/callback', async (c) => {
  const code = c.req.query('code');
  const state = c.req.query('state');
  const error = c.req.query('error');

  const landingOrigin = getLandingOrigin(c);

  if (error || !code || !state) {
    return Response.redirect(`${landingOrigin}/login?error=${encodeURIComponent(error ?? 'cancelled')}`, 302);
  }

  // Validate state
  const stateRecord = await c.env.SESSIONS_KV.get<OAuthStateRecord>(`state:${state}`, 'json');
  if (!stateRecord) {
    return Response.redirect(`${landingOrigin}/login?error=invalid_state`, 302);
  }
  await c.env.SESSIONS_KV.delete(`state:${state}`);

  try {
    let email: string;
    let displayName: string;
    let avatarUrl: string | undefined;
    let providerId: string;
    // provider token 要存進 credential 新家，但必須用最終的 api_key 當租戶鍵，
    // 而 api_key 要等下方 USERS_KV upsert 才決定 → 先暫存，稍後再寫。
    let pendingCredential: { name: string; value: string; service: string } | null = null;
    const provider = stateRecord.provider;
    const redirectUri = 'https://cypher.arcrun.dev/auth/callback';

    if (provider === 'google') {
      // Exchange code for token
      const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
          code,
          client_id: c.env.GOOGLE_CLIENT_ID ?? '',
          client_secret: c.env.GOOGLE_CLIENT_SECRET ?? '',
          redirect_uri: redirectUri,
          grant_type: 'authorization_code',
        }),
      });
      if (!tokenRes.ok) throw new Error('google token exchange failed');
      const tokenData = await tokenRes.json() as { access_token: string; refresh_token?: string };

      // Get user info
      const userRes = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
        headers: { Authorization: `Bearer ${tokenData.access_token}` },
      });
      if (!userRes.ok) throw new Error('google userinfo failed');
      const userInfo = await userRes.json() as {
        sub: string; email: string; name: string; picture?: string;
      };
      email = userInfo.email.toLowerCase();
      displayName = userInfo.name;
      avatarUrl = userInfo.picture;
      providerId = userInfo.sub;

      // 存 Google refresh_token 供 auth_oauth2 零件使用（實際寫入在 apiKey 決定後，見下方
      // pendingCredential）。Google 只在首次授權時回傳 refresh_token，後續登入為 undefined
      if (tokenData.refresh_token) {
        pendingCredential = { name: 'google_refresh_token', value: tokenData.refresh_token, service: 'google_user' };

        // 種 auth_recipe:google_user（用戶自己的 Google OAuth2）
        void upsertAuthRecipe(c.env.RECIPES, {
          kind: 'auth_recipe',
          service: 'google_user',
          version: 1,
          primitive: 'oauth2',
          base_url: 'https://www.googleapis.com',
          display_name: 'Google（用戶帳號）',
          oauth2: {
            token_endpoint: 'https://oauth2.googleapis.com/token',
            client_id: c.env.GOOGLE_CLIENT_ID ?? '',
            client_secret: c.env.GOOGLE_CLIENT_SECRET ?? '',
            scopes: ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/spreadsheets'],
          },
          required_secrets: [{ key: 'google_refresh_token', label: 'Google Refresh Token' }],
          inject: { header: { Authorization: 'Bearer {{runtime.access_token}}' } },
        });
      }

    } else {
      // GitHub: exchange code for token
      const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Accept': 'application/json',
        },
        body: new URLSearchParams({
          code,
          client_id: c.env.GITHUB_CLIENT_ID ?? '',
          client_secret: c.env.GITHUB_CLIENT_SECRET ?? '',
          redirect_uri: redirectUri,
        }),
      });
      if (!tokenRes.ok) throw new Error('github token exchange failed');
      const tokenData = await tokenRes.json() as { access_token: string; token_type?: string };

      // Get user info
      const userRes = await fetch('https://api.github.com/user', {
        headers: {
          Authorization: `Bearer ${tokenData.access_token}`,
          'User-Agent': 'arcrun',
          'Accept': 'application/vnd.github+json',
        },
      });
      if (!userRes.ok) throw new Error('github user fetch failed');
      const userInfo = await userRes.json() as {
        id: number; login: string; name?: string; avatar_url?: string; email?: string;
      };

      // GitHub email might be null if private; fetch emails list
      let ghEmail = userInfo.email ?? '';
      if (!ghEmail) {
        const emailsRes = await fetch('https://api.github.com/user/emails', {
          headers: {
            Authorization: `Bearer ${tokenData.access_token}`,
            'User-Agent': 'arcrun',
            'Accept': 'application/vnd.github+json',
          },
        });
        if (emailsRes.ok) {
          const emails = await emailsRes.json() as { email: string; primary: boolean; verified: boolean }[];
          const primary = emails.find(e => e.primary && e.verified);
          ghEmail = primary?.email ?? emails[0]?.email ?? '';
        }
      }
      if (!ghEmail) throw new Error('github email not available');
      email = ghEmail.toLowerCase();
      displayName = userInfo.name ?? userInfo.login;
      avatarUrl = userInfo.avatar_url;
      providerId = String(userInfo.id);

      // 存 GitHub access_token 供 auth_static_key 零件使用（實際寫入在 apiKey 決定後，見
      // 下方 pendingCredential）。GitHub 沒有 refresh_token，access_token 長效（直到 revoke）
      if (tokenData.access_token) {
        pendingCredential = { name: 'github_access_token', value: tokenData.access_token, service: 'github_user' };

        // GitHub access_token 長效無 refresh 概念，用 static_key primitive
        void upsertAuthRecipe(c.env.RECIPES, {
          kind: 'auth_recipe',
          service: 'github_user',
          version: 1,
          primitive: 'static_key',
          base_url: 'https://api.github.com',
          display_name: 'GitHub（用戶帳號）',
          required_secrets: [{ key: 'github_access_token', label: 'GitHub Access Token' }],
          inject: { header: { Authorization: 'Bearer {{secret.github_access_token}}' } },
        });
      }
    }

    // Upsert user record
    const userKey = `user:${provider}:${providerId}`;
    const existing = await c.env.USERS_KV.get<UserRecord>(userKey, 'json');

    let apiKey: string;
    if (existing && !existing.revoked) {
      // Existing user — keep their api key
      apiKey = existing.api_key;
      // Update display info
      const updated: UserRecord = { ...existing, display_name: displayName, avatar_url: avatarUrl };
      await c.env.USERS_KV.put(userKey, JSON.stringify(updated));
    } else {
      // New user — generate a random api key
      apiKey = generateApiKey();
      const newUser: UserRecord = {
        email, display_name: displayName, avatar_url: avatarUrl,
        api_key: apiKey, provider, provider_id: providerId,
        created_at: new Date().toISOString(),
      };
      await c.env.USERS_KV.put(userKey, JSON.stringify(newUser));
      // Reverse index for API-Key-based auth
      await c.env.USERS_KV.put(`apikey:${apiKey}`, userKey);
    }

    // provider token → credential 新家（Workers Secrets + D1）。登入本身不該因為存
    // credential 失敗而失敗（例如 self-hosted 未設 CF_SECRETS_API_TOKEN）→ 吞錯誤只記 log。
    if (pendingCredential) {
      try {
        await storeCredential(c.env, apiKey, pendingCredential.name, pendingCredential.value, pendingCredential.service);
      } catch (e) {
        console.error('存 provider token 失敗（不影響登入）:', e instanceof Error ? e.message : String(e));
      }
    }

    // Create session (TTL 7 days)
    const sessionId = randomToken(32);
    const session: SessionRecord = {
      user_key: userKey,
      api_key: apiKey,
      email,
      expires_at: Date.now() + 7 * 24 * 60 * 60 * 1000,
    };
    await c.env.SESSIONS_KV.put(`sess:${sessionId}`, JSON.stringify(session), {
      expirationTtl: 7 * 24 * 60 * 60,
    });

    const redirectBack = stateRecord.redirect_back.startsWith('/') ? stateRecord.redirect_back : '/dashboard';
    return new Response(null, {
      status: 302,
      headers: {
        Location: `${landingOrigin}${redirectBack}`,
        'Set-Cookie': `arcrun_session=${sessionId}; Path=/; HttpOnly; Secure; SameSite=Lax; Domain=.arcrun.dev; Max-Age=${7 * 24 * 60 * 60}`,
      },
    });

  } catch (err) {
    console.error('[auth/callback]', err);
    return Response.redirect(`${landingOrigin}/login?error=server_error`, 302);
  }
});

// ─── Logout ───────────────────────────────────────────────────────────────────

authRouter.post('/auth/logout', async (c) => {
  const sessId = getSessionId(c.req.raw);
  if (sessId) {
    await c.env.SESSIONS_KV.delete(`sess:${sessId}`);
  }
  const landingOrigin = getLandingOrigin(c);
  return new Response(null, {
    status: 302,
    headers: {
      Location: `${landingOrigin}/`,
      'Set-Cookie': 'arcrun_session=; Path=/; HttpOnly; Secure; SameSite=Lax; Domain=.arcrun.dev; Max-Age=0',
    },
  });
});

// ─── /me ──────────────────────────────────────────────────────────────────────

authRouter.get('/me', async (c) => {
  const user = await resolveSession(c);
  if (!user) return c.json({ error: 'not authenticated' }, 401);
  return c.json({
    email: user.email,
    display_name: user.display_name,
    avatar_url: user.avatar_url,
    api_key: user.api_key,
    provider: user.provider,
    created_at: user.created_at,
  });
});

// ─── Rotate API Key ───────────────────────────────────────────────────────────

authRouter.put('/me/api-key/rotate', async (c) => {
  const user = await resolveSession(c);
  if (!user) return c.json({ error: 'not authenticated' }, 401);

  // Generate new random key (not HMAC — rotated keys are random)
  const newRaw = randomToken(24);
  const newKey = 'ak_' + newRaw;

  const oldKey = user.api_key;
  const userKey = `user:${user.provider}:${user.provider_id}`;

  const updated: UserRecord = { ...user, api_key: newKey };
  await c.env.USERS_KV.put(userKey, JSON.stringify(updated));

  // Update reverse index
  await c.env.USERS_KV.delete(`apikey:${oldKey}`);
  await c.env.USERS_KV.put(`apikey:${newKey}`, userKey);

  return c.json({
    success: true,
    api_key: newKey,
    message: 'API Key rotated. Your existing workflow credentials are still stored under the old key namespace.',
  });
});

// ─── Revoke API Key ───────────────────────────────────────────────────────────

authRouter.delete('/me/api-key', async (c) => {
  const user = await resolveSession(c);
  if (!user) return c.json({ error: 'not authenticated' }, 401);

  const userKey = `user:${user.provider}:${user.provider_id}`;
  const revoked: UserRecord = { ...user, revoked: true };
  await c.env.USERS_KV.put(userKey, JSON.stringify(revoked));
  await c.env.USERS_KV.delete(`apikey:${user.api_key}`);

  // Clear session cookie
  const sessId = getSessionId(c.req.raw);
  if (sessId) await c.env.SESSIONS_KV.delete(`sess:${sessId}`);

  return new Response(JSON.stringify({ success: true, message: 'API Key revoked.' }), {
    status: 200,
    headers: {
      'Content-Type': 'application/json',
      'Set-Cookie': 'arcrun_session=; Path=/; HttpOnly; Secure; SameSite=Lax; Domain=.arcrun.dev; Max-Age=0',
    },
  });
});
