import { Hono } from 'hono';
import { serveStatic } from 'hono/bun';
import fs from 'fs';
// @ts-ignore Bun project may not have dotenv types configured
import { config } from 'dotenv';

config();

const app = new Hono();

// Minimal log helpers
const logLine = (path: string, line: string) => {
  try {
    fs.appendFileSync(path, `${new Date().toISOString()} ${line}\n`);
  } catch (err) {
    console.error('Failed to write log:', err);
  }
};

// Version endpoint sourced from package.json
app.get('/version', (c) => {
  try {
    const pkgRaw = fs.readFileSync('./package.json', 'utf-8');
    const pkg = JSON.parse(pkgRaw);
    return c.json({ version: pkg.version || '0.0.0' });
  } catch (err) {
    logLine('logs/error.log', `version endpoint error: ${(err as Error)?.message || String(err)}`);
    return c.json({ version: '0.0.0' }, 200);
  }
});

// --- Microsoft Graph helpers ---
const getGraphToken = (c?: any) => {
  const headerToken = c?.req?.header ? c.req.header('x-graph-token') : '';
  const envToken = process.env.GRAPH_TOKEN || process.env.MS_GRAPH_TOKEN || '';
  const token = headerToken || envToken;
  console.log('[getGraphToken] Header token:', headerToken ? headerToken.substring(0, 20) + '...' : 'NONE');
  console.log('[getGraphToken] Env token:', envToken ? 'SET' : 'NOT SET');
  console.log('[getGraphToken] Using:', token ? token.substring(0, 20) + '...' : 'NONE');
  return token;
};

const b64Url = (input: string) =>
  Buffer.from(input).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');

type DriveItem = {
  id: string;
  name: string;
  webUrl?: string;
  size?: number;
  lastModifiedDateTime?: string;
  file?: { mimeType?: string };
  folder?: { childCount?: number };
  parentReference?: { path?: string; driveId?: string };
};

const fetchJson = async (url: string, token: string) => {
  const res = await fetch(url, {
    headers: {
      Authorization: `Bearer ${token}`,
      Accept: 'application/json',
    },
  });
  if (!res.ok) {
    const text = await res.text();
    const err: any = new Error(`Graph ${res.status}: ${text}`);
    err.status = res.status;
    throw err;
  }
  return res.json();
};

const resolveShareLink = async (link: string, token: string) => {
  const encoded = `u!${b64Url(link)}`;
  const data = await fetchJson(`https://graph.microsoft.com/v1.0/shares/${encoded}/driveItem`, token);
  return { driveId: data.parentReference?.driveId as string, itemId: data.id as string };
};

const listChildrenRecursive = async (
  driveId: string,
  itemId: string,
  depth = 0,
  maxDepth = 5,
  token: string,
): Promise<DriveItem[]> => {
  const out: DriveItem[] = [];
  const data = await fetchJson(`https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/children`, token);
  for (const child of data.value || []) {
    out.push(child as DriveItem);
    if (child.folder && depth < maxDepth) {
      const nested = await listChildrenRecursive(driveId, child.id, depth + 1, maxDepth, token);
      out.push(...nested);
    }
  }
  return out;
};

const searchWithinFolder = async (driveId: string, itemId: string, query: string, token: string): Promise<DriveItem[]> => {
  const data = await fetchJson(
    `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/search(q='${encodeURIComponent(query)}')`,
    token,
  );
  return (data.value || []) as DriveItem[];
};

const filterByCategory = (items: DriveItem[], category?: string, pdfOnly: boolean = true) => {
  // First filter: show PDF files and Word documents (which will be converted to PDF)
  let filtered = items;
  if (pdfOnly) {
    filtered = items.filter((i) => {
      if (i.folder) return false;
      const name = i.name.toLowerCase();
      const mimeType = i.file?.mimeType?.toLowerCase() || '';
      // Include PDFs and Word documents
      return name.endsWith('.pdf') || mimeType.includes('pdf') ||
             name.endsWith('.doc') || name.endsWith('.docx') || mimeType.includes('word');
    });
  }
  
  // Second filter: category filtering (if specified)
  if (!category) return filtered;
  const nameHas = (name: string, words: string[]) => words.some((w) => name.includes(w));
  const lc = (s: string) => (s || '').toLowerCase();
  const contractWords = ['contract', 'estimate', 'proposal', 'bid', 'award', 'agreement', 'sow'];
  const planWords = ['plan', 'drawing', 'dwg', 'pdf', 'sheet', 'layout'];
  return filtered.filter((i) => {
    const n = lc(i.name);
    if (category === 'contracts') return nameHas(n, contractWords);
    if (category === 'plans') return nameHas(n, planWords);
    return true;
  });
};

// List/search job files via Graph using a shared folder link
app.get('/api/job-files', async (c) => {
  try {
    console.log('[job-files] Request received');
    const token = getGraphToken(c);
    if (!token) {
      console.log('[job-files] No token found');
      return c.json({ error: 'GRAPH_TOKEN missing' }, 500);
    }
    console.log('[job-files] Token found:', token.substring(0, 20) + '...');
    const link = c.req.query('link');
    const q = c.req.query('q') || '';
    const category = c.req.query('category') || '';
    if (!link) return c.json({ error: 'link required' }, 400);
    console.log('[job-files] Link:', link);

    const { driveId, itemId } = await resolveShareLink(link, token);

    let items: DriveItem[] = [];
    if (q) {
      items = await searchWithinFolder(driveId, itemId, q, token);
    } else {
      // Walk subfolders up to depth 5
      items = await listChildrenRecursive(driveId, itemId, 0, 5, token);
    }

    // Filter to show only PDFs by default (can be disabled with pdfOnly=false query param)
    const pdfOnly = c.req.query('pdfOnly') !== 'false';
    const filtered = filterByCategory(items, category, pdfOnly);

    const mapped = filtered.map((i) => ({
      id: i.id,
      name: i.name,
      url: i.webUrl,
      driveId: i.parentReference?.driveId || driveId,
      size: i.size,
      modified: i.lastModifiedDateTime,
      contentType: i.file?.mimeType,
      isFolder: Boolean(i.folder),
      path: i.parentReference?.path || '',
    }));

    return c.json({ items: mapped, total: mapped.length, source: q ? 'search' : 'walk', driveId });
  } catch (err) {
    const message = (err as Error)?.message || String(err);
    const status = (err as any)?.status || 500;
    logLine('logs/error.log', `/api/job-files error: ${message}`);
    return c.json({ error: message }, status);
  }
});

// Stream file content via Graph to avoid CSP issues for inline preview
app.get('/api/job-file-content', async (c) => {
  try {
    const token = getGraphToken(c);
    if (!token) return c.json({ error: 'GRAPH_TOKEN missing' }, 500);
    const driveId = c.req.query('driveId');
    const itemId = c.req.query('itemId');
    if (!driveId || !itemId) return c.json({ error: 'driveId and itemId required' }, 400);

    const res = await fetch(`https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/content`, {
      headers: { Authorization: `Bearer ${token}` },
    });
    if (!res.ok) {
      const text = await res.text();
      return c.json({ error: `Graph ${res.status}: ${text}` }, res.status);
    }
    const headers: Record<string, string> = {};
    const ct = res.headers.get('content-type');
    const cd = res.headers.get('content-disposition');
    if (ct) headers['Content-Type'] = ct;
    if (cd) headers['Content-Disposition'] = cd;
    return new Response(res.body, { status: 200, headers });
  } catch (err) {
    const message = (err as Error)?.message || String(err);
    const status = (err as any)?.status || 500;
    logLine('logs/error.log', `/api/job-file-content error: ${message}`);
    return c.json({ error: message }, status);
  }
});

// Convert Word documents to PDF for display
app.get('/api/job-file-pdf', async (c) => {
  try {
    const token = getGraphToken(c);
    if (!token) return c.json({ error: 'GRAPH_TOKEN missing' }, 500);
    const driveId = c.req.query('driveId');
    const itemId = c.req.query('itemId');
    if (!driveId || !itemId) return c.json({ error: 'driveId and itemId required' }, 400);

    // Use Graph API to convert to PDF format
    const res = await fetch(`https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/content?format=pdf`, {
      headers: { Authorization: `Bearer ${token}` },
    });
    if (!res.ok) {
      const text = await res.text();
      logLine('logs/error.log', `/api/job-file-pdf conversion failed: ${res.status} - ${text}`);
      return c.json({ error: `PDF conversion failed: ${res.status}` }, res.status);
    }
    const headers: Record<string, string> = {
      'Content-Type': 'application/pdf',
    };
    const cd = res.headers.get('content-disposition');
    if (cd) {
      // Replace .docx/.doc extension with .pdf in filename
      headers['Content-Disposition'] = cd.replace(/\.(docx?)/gi, '.pdf');
    }
    logLine('logs/server.log', `Successfully converted document to PDF: driveId=${driveId}, itemId=${itemId}`);
    return new Response(res.body, { status: 200, headers });
  } catch (err) {
    const message = (err as Error)?.message || String(err);
    const status = (err as any)?.status || 500;
    logLine('logs/error.log', `/api/job-file-pdf error: ${message}`);
    return c.json({ error: message }, status);
  }
});

// Mutation logging endpoint
app.post('/log-change', async (c) => {
  try {
    const body = await c.req.json();
    const { action = 'unknown', user = 'unknown', target = '', detail = '' } = body || {};
    const payload = JSON.stringify({ action, user, target, detail });
    logLine('logs/changes.log', payload);
    return c.json({ status: 'ok' });
  } catch (err) {
    logLine('logs/error.log', `log-change error: ${(err as Error)?.message || String(err)}`);
    return c.text('Bad Request', 400);
  }
});

// Serve static files from the frontend directory (must come after API routes)
app.use('/*', serveStatic({ root: './frontend' }));

// Error handler
app.onError((err, c) => {
  logLine('logs/error.log', `Unhandled error: ${err?.message || err}`);
  return c.text('Internal Server Error', 500);
});

// Default to 3003; can be overridden by Environment=PORT
const PORT = Number(process.env.PORT || 3003);

logLine('logs/server.log', `Starting frontend server on port ${PORT}`);

// Graceful shutdown logging
const shutdown = (signal: string) => {
  logLine('logs/server.log', `Shutting down due to ${signal}`);
  process.exit(0);
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));

export default {
  port: PORT,
  fetch: app.fetch,
};
