import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
import { dirname, resolve } from 'path';
import { homedir } from 'os';
import { google, type Auth } from 'googleapis';
import type { GoogleService } from '../auth/google.js';
import { loadStoredGoogleToken, storeGoogleToken } from '../auth/google.js';

interface GoogleServiceMeta {
  tokenFile: string;
  command: string;
  configPath: string;
}

const GOOGLE_SERVICE_META: Record<GoogleService, GoogleServiceMeta> = {
  gmail: {
    tokenFile: '~/.config/flynn/gmail-token.json',
    command: 'gmail-auth',
    configPath: 'automation.gmail',
  },
  gcal: {
    tokenFile: '~/.config/flynn/gcal-token.json',
    command: 'gcal-auth',
    configPath: 'automation.gcal',
  },
  gdocs: {
    tokenFile: '~/.config/flynn/gdocs-token.json',
    command: 'gdocs-auth',
    configPath: 'automation.gdocs',
  },
  gdrive: {
    tokenFile: '~/.config/flynn/gdrive-token.json',
    command: 'gdrive-auth',
    configPath: 'automation.gdrive',
  },
  gtasks: {
    tokenFile: '~/.config/flynn/gtasks-token.json',
    command: 'gtasks-auth',
    configPath: 'automation.gtasks',
  },
};

export interface GoogleOAuthCredentials {
  client_id: string;
  client_secret: string;
  redirect_uris?: string[];
}

export interface CreateGoogleOAuthClientOptions {
  service: GoogleService;
  credentialsFile?: string;
  tokenFile?: string;
}

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

export function readGoogleOAuthCredentials(
  service: GoogleService,
  credentialsFile?: string,
): GoogleOAuthCredentials {
  if (!credentialsFile) {
    throw new Error(`No credentials_file configured. Set ${GOOGLE_SERVICE_META[service].configPath}.credentials_file in config.`);
  }

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

  const credentials = JSON.parse(readFileSync(credentialsPath, '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');
  }

  return { client_id, client_secret, redirect_uris };
}

export function tokenPathForGoogleService(service: GoogleService, configuredTokenPath?: string): string {
  return expandPath(configuredTokenPath ?? GOOGLE_SERVICE_META[service].tokenFile);
}

function readTokenFromFile(tokenPath: string): Record<string, unknown> | null {
  if (!existsSync(tokenPath)) {
    return null;
  }
  try {
    return JSON.parse(readFileSync(tokenPath, 'utf-8')) as Record<string, unknown>;
  } catch {
    return null;
  }
}

function writeTokenToFile(tokenPath: string, token: Record<string, unknown>): void {
  const dir = dirname(tokenPath);
  if (!existsSync(dir)) {
    mkdirSync(dir, { recursive: true });
  }
  writeFileSync(tokenPath, JSON.stringify(token, null, 2), 'utf-8');
  try {
    chmodSync(tokenPath, 0o600);
  } catch {
    // chmod may fail on some filesystems
  }
}

function loadTokenWithMigration(service: GoogleService, tokenPath: string, credentialsPath: string): Record<string, unknown> | null {
  const fromStore = loadStoredGoogleToken(service);
  if (fromStore) {
    return fromStore;
  }

  const fromFile = readTokenFromFile(tokenPath);
  if (!fromFile) {
    return null;
  }

  storeGoogleToken(service, fromFile, {
    tokenFile: tokenPath,
    credentialsFile: credentialsPath,
  });
  return fromFile;
}

export function authCommandForGoogleService(service: GoogleService): string {
  return GOOGLE_SERVICE_META[service].command;
}

export function createGoogleOAuth2Client(options: CreateGoogleOAuthClientOptions): Auth.OAuth2Client {
  const { service, credentialsFile, tokenFile } = options;
  const creds = readGoogleOAuthCredentials(service, credentialsFile);
  const credentialsPath = expandPath(credentialsFile ?? '');
  const resolvedTokenPath = tokenPathForGoogleService(service, tokenFile);

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

  let currentToken = loadTokenWithMigration(service, resolvedTokenPath, credentialsPath);
  if (!currentToken) {
    throw new Error(
      `Token file not found: ${resolvedTokenPath}. Run "flynn ${authCommandForGoogleService(service)}" to authenticate.`,
    );
  }

  oauth2Client.setCredentials(currentToken);
  if (typeof oauth2Client.on === 'function') {
    oauth2Client.on('tokens', (newTokens) => {
      currentToken = { ...currentToken, ...newTokens };
      storeGoogleToken(service, currentToken, {
        tokenFile: resolvedTokenPath,
        credentialsFile: credentialsPath,
      });
      writeTokenToFile(resolvedTokenPath, currentToken);
    });
  }

  return oauth2Client;
}
