import { google, type Auth } from 'googleapis';
import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { homedir } from 'os';
import type { GdriveConfig } 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 config (credentials + token files). */
function createOAuth2Client(config: NonNullable<GdriveConfig>): Auth.OAuth2Client {
  const credentialsPath = config.credentials_file;
  if (!credentialsPath) {
    throw new Error('No credentials_file configured. Set automation.gdrive.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/gdrive-token.json');
  if (!existsSync(tokenPath)) {
    throw new Error(`Token file not found: ${tokenPath}. Run "flynn gdrive-auth" to authenticate.`);
  }

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

  return oauth2Client;
}

interface FileSummary {
  id: string;
  name: string;
  mimeType: string;
  modifiedTime: string;
  size: string;
  owners: string[];
  webViewLink: string;
}

/** Map Google Workspace MIME types to human-readable labels. */
function friendlyMimeType(mimeType: string): string {
  const map: Record<string, string> = {
    'application/vnd.google-apps.document': 'Google Doc',
    'application/vnd.google-apps.spreadsheet': 'Google Sheet',
    'application/vnd.google-apps.presentation': 'Google Slides',
    'application/vnd.google-apps.form': 'Google Form',
    'application/vnd.google-apps.drawing': 'Google Drawing',
    'application/vnd.google-apps.folder': 'Folder',
    'application/pdf': 'PDF',
  };
  return map[mimeType] ?? mimeType;
}

/** Format file size in human-readable form. */
function formatSize(bytes: string | undefined): string {
  if (!bytes) {return '';}
  const n = parseInt(bytes, 10);
  if (isNaN(n)) {return '';}
  if (n < 1024) {return `${n} B`;}
  if (n < 1024 * 1024) {return `${(n / 1024).toFixed(1)} KB`;}
  return `${(n / (1024 * 1024)).toFixed(1)} MB`;
}

/** Format a list of file summaries for tool output. */
function formatFiles(files: FileSummary[]): string {
  if (files.length === 0) {
    return 'No files found.';
  }

  return files
    .map(f => {
      const parts = [`[${f.id}] ${f.name}`];
      parts.push(`  Type: ${friendlyMimeType(f.mimeType)}`);
      parts.push(`  Modified: ${f.modifiedTime}`);
      const size = formatSize(f.size);
      if (size) {parts.push(`  Size: ${size}`);}
      if (f.owners.length > 0) {parts.push(`  Owners: ${f.owners.join(', ')}`);}
      if (f.webViewLink) {parts.push(`  Link: ${f.webViewLink}`);}
      return parts.join('\n');
    })
    .join('\n\n');
}

/** Google Workspace MIME types that can be exported as plain text. */
const EXPORTABLE_TYPES: Record<string, { exportMime: string; label: string }> = {
  'application/vnd.google-apps.document': { exportMime: 'text/plain', label: 'Google Doc' },
  'application/vnd.google-apps.spreadsheet': { exportMime: 'text/csv', label: 'Google Sheet' },
  'application/vnd.google-apps.presentation': { exportMime: 'text/plain', label: 'Google Slides' },
};

/** Text MIME types that can be read directly. */
function isTextMime(mimeType: string): boolean {
  return mimeType.startsWith('text/') ||
    mimeType === 'application/json' ||
    mimeType === 'application/xml' ||
    mimeType === 'application/javascript' ||
    mimeType === 'application/x-yaml' ||
    mimeType.endsWith('+json') ||
    mimeType.endsWith('+xml');
}

/**
 * Creates Google Drive read-only tools. Shares config/token with gdocs.
 * Provides list, search, and read (with export for Workspace files).
 */
export function createGdriveTools(config: NonNullable<GdriveConfig>): Tool[] {
  const driveList: Tool = {
    name: 'drive.list',
    description:
      'List recent files from Google Drive. Returns id, name, type, modified time, size, owners, and link.',
    requiredSecretScopes: ['gdrive'],
    inputSchema: {
      type: 'object',
      properties: {
        maxResults: {
          type: 'number',
          description: 'Maximum number of files to return (default: 10)',
        },
        mimeType: {
          type: 'string',
          description: 'Filter by MIME type (e.g. "application/pdf", "application/vnd.google-apps.spreadsheet")',
        },
        folderId: {
          type: 'string',
          description: 'List files within a specific folder ID',
        },
      },
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { maxResults?: number; mimeType?: string; folderId?: string };
      const maxResults = args.maxResults ?? 10;

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

        const queryParts = ['trashed=false'];
        if (args.mimeType) {
          queryParts.push(`mimeType='${args.mimeType}'`);
        }
        if (args.folderId) {
          queryParts.push(`'${args.folderId}' in parents`);
        }

        const response = await drive.files.list({
          q: queryParts.join(' and '),
          pageSize: maxResults,
          orderBy: 'modifiedTime desc',
          fields: 'files(id,name,mimeType,modifiedTime,size,owners,webViewLink)',
        });

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

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

  const driveSearch: Tool = {
    name: 'drive.search',
    description:
      'Search Google Drive files by name or content. Returns id, name, type, modified time, size, owners, and link.',
    requiredSecretScopes: ['gdrive'],
    inputSchema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Search query to match against file names and content',
        },
        maxResults: {
          type: 'number',
          description: 'Maximum number of results to return (default: 10)',
        },
        mimeType: {
          type: 'string',
          description: 'Filter by MIME type (e.g. "application/pdf")',
        },
      },
      required: ['query'],
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { query: string; maxResults?: number; mimeType?: string };
      const maxResults = args.maxResults ?? 10;

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

        const escapedQuery = args.query.replace(/'/g, "\\'");
        const queryParts = [
          'trashed=false',
          `(name contains '${escapedQuery}' or fullText contains '${escapedQuery}')`,
        ];
        if (args.mimeType) {
          queryParts.push(`mimeType='${args.mimeType}'`);
        }

        const response = await drive.files.list({
          q: queryParts.join(' and '),
          pageSize: maxResults,
          orderBy: 'modifiedTime desc',
          fields: 'files(id,name,mimeType,modifiedTime,size,owners,webViewLink)',
        });

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

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

  const driveRead: Tool = {
    name: 'drive.read',
    description:
      'Read the content of a Google Drive file. Exports Google Workspace files (Docs, Sheets, Slides) as plain text/CSV. Downloads regular text files directly. Use drive.list or drive.search to get file IDs.',
    requiredSecretScopes: ['gdrive'],
    inputSchema: {
      type: 'object',
      properties: {
        fileId: {
          type: 'string',
          description: 'The Google Drive file ID (from drive.list or drive.search results)',
        },
      },
      required: ['fileId'],
    },
    execute: async (rawArgs: unknown): Promise<ToolResult> => {
      const args = rawArgs as { fileId: string };

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

        // First get file metadata to determine type
        const meta = await drive.files.get({
          fileId: args.fileId,
          fields: 'id,name,mimeType,size',
        });

        const name = meta.data.name ?? '(untitled)';
        const mimeType = meta.data.mimeType ?? '';

        // Google Workspace files: export
        const exportable = EXPORTABLE_TYPES[mimeType];
        if (exportable) {
          const exported = await drive.files.export({
            fileId: args.fileId,
            mimeType: exportable.exportMime,
          }, { responseType: 'text' });

          const content = typeof exported.data === 'string' ? exported.data : String(exported.data);

          return {
            success: true,
            output: `Name: ${name}\nType: ${exportable.label}\n\n${content || '(empty)'}`,
          };
        }

        // Regular text files: download
        if (isTextMime(mimeType)) {
          const downloaded = await drive.files.get({
            fileId: args.fileId,
            alt: 'media',
          }, { responseType: 'text' });

          const content = typeof downloaded.data === 'string' ? downloaded.data : String(downloaded.data);

          return {
            success: true,
            output: `Name: ${name}\nType: ${mimeType}\n\n${content || '(empty)'}`,
          };
        }

        // Binary / unsupported types
        return {
          success: true,
          output: `Name: ${name}\nType: ${friendlyMimeType(mimeType)}\n\nBinary file — cannot display content. Use the web link to view this file.`,
        };
      } catch (error) {
        return {
          success: false,
          output: '',
          error: error instanceof Error ? error.message : String(error),
        };
      }
    },
  };

  return [driveList, driveSearch, driveRead];
}
