// Cloudflare Worker - 仅 API 后端（前后端分离）
// 部署到 cf-nav-backend，前端静态资源由 Cloudflare Pages 托管

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};

function verifyAuth(request, env) {
  const auth = request.headers.get('Authorization');
  const token = (env.ADMIN_PASSWORD || env.ADMIN_TOKEN || '').trim();
  if (!token) return false;
  const prefix = 'Bearer ';
  if (!auth || !auth.startsWith(prefix)) return false;
  const provided = auth.slice(prefix.length).trim();
  return provided === token;
}

export default {
  async fetch(request, env) {
    if (request.method === 'OPTIONS') {
      return new Response(null, { headers: corsHeaders });
    }

    const url = new URL(request.url);
    const path = url.pathname;

    try {
      if (path === '/api/sites') {
        return handleSites(request, env, corsHeaders);
      }
      if (path === '/api/auth/check') {
        return handleAuthCheck(request, env, corsHeaders);
      }
      if (path.match(/^\/api\/sites\/[^/]+\/click$/)) {
        const id = path.split('/')[3];
        return handleSiteClick(request, env, id, corsHeaders);
      }
      if (path.startsWith('/api/sites/')) {
        const id = path.split('/')[3];
        return handleSite(request, env, id, corsHeaders);
      }
      if (path === '/api/categories') {
        return handleCategories(request, env, corsHeaders);
      }
      if (path.startsWith('/api/categories/')) {
        const name = decodeURIComponent(path.split('/')[3] || '');
        return handleCategory(request, env, name, corsHeaders);
      }
      if (path === '/api/favicon') {
        return handleFavicon(request, env, corsHeaders);
      }

      return new Response('Not Found', { status: 404 });
    } catch (error) {
      return new Response('Internal Server Error: ' + error.message, {
        status: 500,
        headers: corsHeaders,
      });
    }
  },
};

/** 校验管理员 token，用于前端进入后台时确认链接有效 */
async function handleAuthCheck(request, env, corsHeaders) {
  if (request.method !== 'GET') {
    return new Response('Method not allowed', { status: 405, headers: corsHeaders });
  }
  const token = (env.ADMIN_PASSWORD || env.ADMIN_TOKEN || '').trim();
  if (!token) {
    return new Response(JSON.stringify({ ok: false }), {
      status: 401,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  const auth = request.headers.get('Authorization');
  const prefix = 'Bearer ';
  if (!auth || !auth.startsWith(prefix)) {
    return new Response(JSON.stringify({ ok: false }), {
      status: 401,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  const provided = auth.slice(prefix.length).trim();
  if (provided !== token) {
    return new Response(JSON.stringify({ ok: false }), {
      status: 401,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  return new Response(JSON.stringify({ ok: true }), {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  });
}

async function handleFavicon(request, env, corsHeaders) {
  const url = new URL(request.url);
  const domain = url.searchParams.get('domain');
  if (!domain) {
    return new Response('Missing domain parameter', { status: 400, headers: corsHeaders });
  }
  const faviconApi = env.FAVICON_API || env.FAVICON;
  if (faviconApi && faviconApi !== '') {
    const targetUrl = domain.startsWith('http') ? domain : `https://${domain}`;
    try {
      const res = await fetch(faviconApi + encodeURIComponent(targetUrl), {
        headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
        cf: { cacheTtl: 86400, cacheEverything: true },
      });
      if (res.ok) {
        return new Response(res.body, {
          status: res.status,
          headers: {
            ...corsHeaders,
            'Content-Type': res.headers.get('Content-Type') || 'image/x-icon',
            'Cache-Control': 'public, max-age=86400',
          },
        });
      }
    } catch (_) {
      /* 外置 API 失败时回退到内置源 */
    }
  }
  const faviconSources = [
    `https://www.google.com/s2/favicons?domain=${domain}&sz=64`,
    `https://icons.duckduckgo.com/ip3/${domain}.ico`,
    `https://favicon.api.shumengya.top/${domain}`,
  ];
  for (const source of faviconSources) {
    try {
      const response = await fetch(source, {
        headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
        cf: { cacheTtl: 86400, cacheEverything: true },
      });
      if (response.ok) {
        return new Response(response.body, {
          status: response.status,
          headers: {
            ...corsHeaders,
            'Content-Type': response.headers.get('Content-Type') || 'image/x-icon',
            'Cache-Control': 'public, max-age=86400',
          },
        });
      }
    } catch (_) {
      continue;
    }
  }
  return new Response('Favicon not found', { status: 404, headers: corsHeaders });
}

async function handleSites(request, env, corsHeaders) {
  if (request.method === 'GET') {
    const sitesData = (await env.NAV_KV.get('sites', { type: 'json' })) || [];
    const normalized = sitesData.map((s) => ({ ...s, clicks: typeof s.clicks === 'number' ? s.clicks : 0 }));
    return new Response(JSON.stringify(normalized), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  if (request.method === 'POST') {
    if (!verifyAuth(request, env)) {
      return new Response('Unauthorized', { status: 401, headers: corsHeaders });
    }
    const newSite = await request.json();
    const sites = (await env.NAV_KV.get('sites', { type: 'json' })) || [];
    const categories = (await env.NAV_KV.get('categories', { type: 'json' })) || [];
    newSite.id = Date.now().toString();
    newSite.clicks = 0;
    sites.push(newSite);
    if (newSite.category && !categories.includes(newSite.category)) {
      categories.push(newSite.category);
      await env.NAV_KV.put('categories', JSON.stringify(categories));
    }
    await env.NAV_KV.put('sites', JSON.stringify(sites));
    return new Response(JSON.stringify(newSite), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  return new Response('Method not allowed', { status: 405, headers: corsHeaders });
}

async function handleSite(request, env, id, corsHeaders) {
  const sites = (await env.NAV_KV.get('sites', { type: 'json' })) || [];
  const categories = (await env.NAV_KV.get('categories', { type: 'json' })) || [];

  if (request.method === 'GET') {
    const site = sites.find((s) => s.id === id);
    if (!site) return new Response('Not Found', { status: 404, headers: corsHeaders });
    const normalized = { ...site, clicks: typeof site.clicks === 'number' ? site.clicks : 0 };
    return new Response(JSON.stringify(normalized), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  if (request.method === 'PUT') {
    if (!verifyAuth(request, env)) {
      return new Response('Unauthorized', { status: 401, headers: corsHeaders });
    }
    const updatedSite = await request.json();
    const index = sites.findIndex((s) => s.id === id);
    if (index === -1) return new Response('Not Found', { status: 404, headers: corsHeaders });
    const existingClicks = typeof sites[index].clicks === 'number' ? sites[index].clicks : 0;
    sites[index] = { ...sites[index], ...updatedSite, id, clicks: existingClicks };
    if (updatedSite.category && !categories.includes(updatedSite.category)) {
      categories.push(updatedSite.category);
      await env.NAV_KV.put('categories', JSON.stringify(categories));
    }
    await env.NAV_KV.put('sites', JSON.stringify(sites));
    return new Response(JSON.stringify(sites[index]), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  if (request.method === 'DELETE') {
    if (!verifyAuth(request, env)) {
      return new Response('Unauthorized', { status: 401, headers: corsHeaders });
    }
    const index = sites.findIndex((s) => s.id === id);
    if (index === -1) return new Response('Not Found', { status: 404, headers: corsHeaders });
    sites.splice(index, 1);
    await env.NAV_KV.put('sites', JSON.stringify(sites));
    return new Response(JSON.stringify({ success: true }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  return new Response('Method not allowed', { status: 405, headers: corsHeaders });
}

async function handleSiteClick(request, env, id, corsHeaders) {
  if (request.method !== 'POST') {
    return new Response('Method not allowed', { status: 405, headers: corsHeaders });
  }
  const sites = (await env.NAV_KV.get('sites', { type: 'json' })) || [];
  const index = sites.findIndex((s) => s.id === id);
  if (index === -1) {
    return new Response('Not Found', { status: 404, headers: corsHeaders });
  }
  const site = sites[index];
  const prev = typeof site.clicks === 'number' ? site.clicks : 0;
  sites[index] = { ...site, clicks: prev + 1 };
  await env.NAV_KV.put('sites', JSON.stringify(sites));
  return new Response(JSON.stringify({ success: true, clicks: prev + 1 }), {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  });
}

async function handleCategories(request, env, corsHeaders) {
  if (request.method === 'GET') {
    let categories = (await env.NAV_KV.get('categories', { type: 'json' })) || [];
    if (!categories.length) {
      const sites = (await env.NAV_KV.get('sites', { type: 'json' })) || [];
      categories = [...new Set(sites.map((s) => s.category).filter(Boolean))];
      if (categories.length) await env.NAV_KV.put('categories', JSON.stringify(categories));
    }
    return new Response(JSON.stringify(categories), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  if (request.method === 'POST') {
    if (!verifyAuth(request, env)) {
      return new Response('Unauthorized', { status: 401, headers: corsHeaders });
    }
    const { name } = await request.json();
    if (!name || !name.trim()) {
      return new Response('Bad Request', { status: 400, headers: corsHeaders });
    }
    const categories = (await env.NAV_KV.get('categories', { type: 'json' })) || [];
    if (!categories.includes(name.trim())) {
      categories.push(name.trim());
      await env.NAV_KV.put('categories', JSON.stringify(categories));
    }
    return new Response(JSON.stringify({ success: true }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  return new Response('Method not allowed', { status: 405, headers: corsHeaders });
}

async function handleCategory(request, env, name, corsHeaders) {
  if (!name) return new Response('Bad Request', { status: 400, headers: corsHeaders });
  if (!verifyAuth(request, env)) {
    return new Response('Unauthorized', { status: 401, headers: corsHeaders });
  }
  const categories = (await env.NAV_KV.get('categories', { type: 'json' })) || [];

  if (request.method === 'DELETE') {
    const filtered = categories.filter((c) => c !== name);
    await env.NAV_KV.put('categories', JSON.stringify(filtered));
    return new Response(JSON.stringify({ success: true }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  if (request.method === 'PUT') {
    const body = await request.json();
    const newName = (body?.name || '').trim();
    if (!newName) return new Response('Bad Request', { status: 400, headers: corsHeaders });
    const updated = categories.map((c) => (c === name ? newName : c));
    const unique = Array.from(new Set(updated));
    await env.NAV_KV.put('categories', JSON.stringify(unique));
    const sites = (await env.NAV_KV.get('sites', { type: 'json' })) || [];
    const updatedSites = sites.map((site) =>
      site.category === name ? { ...site, category: newName } : site
    );
    await env.NAV_KV.put('sites', JSON.stringify(updatedSites));
    return new Response(JSON.stringify({ success: true }), {
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
    });
  }
  return new Response('Method not allowed', { status: 405, headers: corsHeaders });
}
