import { google, type Auth } from 'googleapis';
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { homedir } from 'os';
import type { GdocsConfig } from '../../config/schema.js';
import type { Tool, ToolResult } from '../types.js';

/** Expand ~ to home directory. */
function expandPath(p: string): string {
  if (p.startsWith('~/') || p === '~') {
    return resolve(homedir(), p.slice(2));
  }
  return resolve(p);
}

/** Create an OAuth2 client from Google Docs config (credentials + token files). */
function createOAuth2Client(config: NonNullable<GdocsConfig>): Auth.OAuth2Client {
  const credentialsPath = config.credentials_file;
  if (!credentialsPath) {
    throw new Error('No credentials_file configured. Set automation.gdocs.credentials_file in config.');
  }

  const expandedCredsPath = expandPath(credentialsPath);
  if (!existsSync(expandedCredsPath)) {
    throw new Error(`Credentials file not found: ${expandedCredsPath}`);
  }

  const credentials = JSON.parse(readFileSync(expandedCredsPath, 'utf-8'));
  const { client_id, client_secret, redirect_uris } = credentials.installed ?? credentials.web ?? {};

  if (!client_id || !client_secret) {
    throw new Error('Invalid credentials file — missing client_id or client_secret');
  }

  const oauth2Client = new google.auth.OAuth2(
    client_id,
    client_secret,
    redirect_uris?.[0] ?? 'http://localhost',
  );

  const tokenPath = expandPath(config.token_file ?? '~/.config/flynn/gdocs-token.json');
  if (!existsSync(tokenPath)) {
    throw new Error(`Token file not found: ${tokenPath}. Run "flynn gdocs-auth" to authenticate.`);
  }

  const token = JSON.parse(readFileSync(tokenPath, 'utf-8'));
  oauth2Client.setCredentials(token);

  return oauth2Client;
}

interface DocSummary {
  id: string;
  name: string;
  modifiedTime: string;
  owners: string[];
  webViewLink: string;
}

/** Format a list of document summaries for tool output. */
function formatDocs(docs: DocSummary[]): string {
  if (docs.length === 0) {
    return 'No documents found.';
  }

  return docs
    .map(d => {
      const parts = [`[${d.id}] ${d.name}`];
      parts.push(`  Modified: ${d.modifiedTime}`);
      if (d.owners.length > 0) {parts.push(`  Owners: ${d.owners.join(', ')}`);}
      if (d.webViewLink) {parts.push(`  Link: ${d.webViewLink}`);}
      return parts.join('\n');
    })
    .join('\n\n');
}

/** Extract plain text from a Google Docs document body. */
function extractPlainText(body: import('googleapis').docs_v1.Schema$Body): string {
  if (!body.content) {return '';}

  const parts: string[] = [];
  for (const structural of body.content) {
    if (structural.paragraph?.elements) {
      for (const element of structural.paragraph.elements) {
        if (element.textRun?.content) {
          parts.push(element.textRun.content);
        }
      }
    }
  }
  return parts.join('');
}

/**
 * Creates Google Docs read-only tools bound to the given GdocsConfig.
 * Uses Drive API for list/search and Docs API for reading content.
 */
export function createGdocsTools(config: NonNullable<GdocsConfig>): Tool[] {
  const docsList: Tool = {
    name: 'docs.list',
    description:
      'List recent Google Docs documents. Returns id, name, modified time, owners, and link for each document.',
    requiredSecretScopes: ['gdocs'],
    inputSchema: {
      type: 'object',
      properties: {
        maxResults: {
          type: 'number',
          description: 'Maximum number of documents to return (default: 10)',
        },
      },
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { maxResults?: number };
      const maxResults = args.maxResults ?? 10;

      try {
        const auth = createOAuth2Client(config);
        const drive = google.drive({ version: 'v3', auth });

        const response = await drive.files.list({
          q: "mimeType='application/vnd.google-apps.document' and trashed=false",
          pageSize: maxResults,
          orderBy: 'modifiedTime desc',
          fields: 'files(id,name,modifiedTime,owners,webViewLink)',
        });

        const files = response.data.files ?? [];
        const docs: DocSummary[] = files.map(f => ({
          id: f.id ?? '',
          name: f.name ?? '(untitled)',
          modifiedTime: f.modifiedTime ?? '',
          owners: (f.owners ?? []).map(o => o.displayName ?? o.emailAddress ?? '').filter(Boolean),
          webViewLink: f.webViewLink ?? '',
        }));

        return {
          success: true,
          output: formatDocs(docs),
        };
      } catch (error) {
        return {
          success: false,
          output: '',
          error: error instanceof Error ? error.message : String(error),
        };
      }
    },
  };

  const docsSearch: Tool = {
    name: 'docs.search',
    description:
      'Search Google Docs by name. Returns id, name, modified time, owners, and link for each matching document.',
    requiredSecretScopes: ['gdocs'],
    inputSchema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Search query to match against document names',
        },
        maxResults: {
          type: 'number',
          description: 'Maximum number of results to return (default: 10)',
        },
      },
      required: ['query'],
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { query: string; maxResults?: number };
      const maxResults = args.maxResults ?? 10;

      try {
        const auth = createOAuth2Client(config);
        const drive = google.drive({ version: 'v3', auth });

        const escapedQuery = args.query.replace(/'/g, "\\'");
        const response = await drive.files.list({
          q: `mimeType='application/vnd.google-apps.document' and trashed=false and (name contains '${escapedQuery}' or fullText contains '${escapedQuery}')`,
          pageSize: maxResults,
          orderBy: 'modifiedTime desc',
          fields: 'files(id,name,modifiedTime,owners,webViewLink)',
        });

        const files = response.data.files ?? [];
        const docs: DocSummary[] = files.map(f => ({
          id: f.id ?? '',
          name: f.name ?? '(untitled)',
          modifiedTime: f.modifiedTime ?? '',
          owners: (f.owners ?? []).map(o => o.displayName ?? o.emailAddress ?? '').filter(Boolean),
          webViewLink: f.webViewLink ?? '',
        }));

        return {
          success: true,
          output: formatDocs(docs),
        };
      } catch (error) {
        return {
          success: false,
          output: '',
          error: error instanceof Error ? error.message : String(error),
        };
      }
    },
  };

  const docsRead: Tool = {
    name: 'docs.read',
    description:
      'Read the content of a Google Doc as plain text. Use docs.list or docs.search first to get document IDs.',
    requiredSecretScopes: ['gdocs'],
    inputSchema: {
      type: 'object',
      properties: {
        documentId: {
          type: 'string',
          description: 'The Google Docs document ID (from docs.list or docs.search results)',
        },
      },
      required: ['documentId'],
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { documentId: string };

      try {
        const auth = createOAuth2Client(config);
        const docs = google.docs({ version: 'v1', auth });

        const doc = await docs.documents.get({
          documentId: args.documentId,
        });

        const title = doc.data.title ?? '(untitled)';
        const body = doc.data.body ? extractPlainText(doc.data.body) : '';

        const parts = [
          `Title: ${title}`,
          '',
          body || '(empty document)',
        ];

        return {
          success: true,
          output: parts.join('\n'),
        };
      } catch (error) {
        return {
          success: false,
          output: '',
          error: error instanceof Error ? error.message : String(error),
        };
      }
    },
  };

  return [docsList, docsSearch, docsRead];
}
