import { google } from 'googleapis';
import { config } from '../config.js';
import { getDb } from '../db/index.js';
import { intakeService } from './intake.service.js';
import type { AttachmentMeta, IngestInput } from '../types.js';

interface GmailTokenRow {
  access_token: string;
  refresh_token: string | null;
  expiry_date: number | null;
  account_email: string | null;
}

export class GmailService {
  private oauth2Client = config.gmail.enabled()
    ? new google.auth.OAuth2(
        config.gmail.clientId,
        config.gmail.clientSecret,
        config.gmail.redirectUri,
      )
    : null;

  getAuthUrl(): string | null {
    if (!this.oauth2Client) return null;
    return this.oauth2Client.generateAuthUrl({
      access_type: 'offline',
      prompt: 'consent',
      scope: [
        'https://www.googleapis.com/auth/gmail.readonly',
        'https://www.googleapis.com/auth/gmail.modify',
      ],
    });
  }

  async handleCallback(code: string): Promise<{ email: string | null }> {
    if (!this.oauth2Client) throw new Error('Gmail not configured');
    const { tokens } = await this.oauth2Client.getToken(code);
    this.oauth2Client.setCredentials(tokens);

    const gmail = google.gmail({ version: 'v1', auth: this.oauth2Client });
    const profile = await gmail.users.getProfile({ userId: 'me' });
    const email = profile.data.emailAddress ?? null;

    const db = getDb();
    db.prepare(
      `INSERT INTO gmail_tokens (id, account_email, access_token, refresh_token, expiry_date, updated_at)
       VALUES ('default', ?, ?, ?, ?, ?)
       ON CONFLICT(id) DO UPDATE SET
         account_email = excluded.account_email,
         access_token = excluded.access_token,
         refresh_token = COALESCE(excluded.refresh_token, gmail_tokens.refresh_token),
         expiry_date = excluded.expiry_date,
         updated_at = excluded.updated_at`,
    ).run(
      email,
      tokens.access_token,
      tokens.refresh_token ?? null,
      tokens.expiry_date ?? null,
      new Date().toISOString(),
    );

    return { email };
  }

  getStatus(): { connected: boolean; email: string | null } {
    const row = getDb()
      .prepare('SELECT account_email FROM gmail_tokens WHERE id = ?')
      .get('default') as { account_email: string | null } | undefined;
    return { connected: Boolean(row), email: row?.account_email ?? null };
  }

  private async getAuthenticatedClient() {
    if (!this.oauth2Client) return null;
    const row = getDb()
      .prepare('SELECT * FROM gmail_tokens WHERE id = ?')
      .get('default') as GmailTokenRow | undefined;
    if (!row) return null;

    this.oauth2Client.setCredentials({
      access_token: row.access_token,
      refresh_token: row.refresh_token ?? undefined,
      expiry_date: row.expiry_date ?? undefined,
    });

    if (row.expiry_date && row.expiry_date < Date.now() + 60000 && row.refresh_token) {
      const { credentials } = await this.oauth2Client.refreshAccessToken();
      this.oauth2Client.setCredentials(credentials);
      getDb()
        .prepare(
          'UPDATE gmail_tokens SET access_token = ?, expiry_date = ?, updated_at = ? WHERE id = ?',
        )
        .run(
          credentials.access_token,
          credentials.expiry_date ?? null,
          new Date().toISOString(),
          'default',
        );
    }

    return this.oauth2Client;
  }

  async poll(): Promise<{ imported: number; duplicates: number; errors: string[] }> {
    const auth = await this.getAuthenticatedClient();
    if (!auth) return { imported: 0, duplicates: 0, errors: ['Gmail not connected'] };

    const gmail = google.gmail({ version: 'v1', auth });
    const status = this.getStatus();
    let imported = 0;
    let duplicates = 0;
    const errors: string[] = [];

    try {
      const list = await gmail.users.messages.list({
        userId: 'me',
        q: config.gmail.searchQuery,
        maxResults: 20,
      });

      for (const msg of list.data.messages ?? []) {
        if (!msg.id) continue;
        try {
          const full = await gmail.users.messages.get({
            userId: 'me',
            id: msg.id,
            format: 'full',
          });

          const input = this.normalizeMessage(full.data, status.email);
          const result = intakeService.ingest(input);
          if (result.duplicate) duplicates++;
          else imported++;

          await gmail.users.messages.modify({
            userId: 'me',
            id: msg.id,
            requestBody: { addLabelIds: [], removeLabelIds: [] },
          });
        } catch (err) {
          errors.push(`Message ${msg.id}: ${err instanceof Error ? err.message : String(err)}`);
        }
      }
    } catch (err) {
      errors.push(err instanceof Error ? err.message : String(err));
    }

    return { imported, duplicates, errors };
  }

  private normalizeMessage(
    message: {
      id?: string | null;
      threadId?: string | null;
      snippet?: string | null;
      payload?: {
        headers?: Array<{ name?: string | null; value?: string | null }>;
        parts?: Array<{
          mimeType?: string | null;
          filename?: string | null;
          body?: { size?: number | null; attachmentId?: string | null };
          parts?: unknown[];
        }>;
        body?: { data?: string | null };
      };
      internalDate?: string | null;
    },
    accountEmail: string | null,
  ): IngestInput {
    const headers = message.payload?.headers ?? [];
    const getHeader = (name: string) =>
      headers.find((h) => h.name?.toLowerCase() === name.toLowerCase())?.value ?? '';

    const from = getHeader('From');
    const subject = getHeader('Subject') || '(sans sujet)';
    const to = getHeader('To');
    const dateHeader = getHeader('Date');

    const emailMatch = from.match(/<([^>]+)>/) ?? from.match(/([\w.+-]+@[\w.-]+)/);
    const nameMatch = from.match(/^([^<]+)</);
    const senderEmail = emailMatch?.[1] ?? null;
    const senderName = nameMatch?.[1]?.trim().replace(/"/g, '') ?? from.trim();

    const body = this.extractBody(message.payload as Parameters<typeof this.extractBody>[0]);
    const attachments = this.extractAttachments(message.payload);

    const receivedAt = message.internalDate
      ? new Date(parseInt(message.internalDate, 10)).toISOString()
      : dateHeader
        ? new Date(dateHeader).toISOString()
        : new Date().toISOString();

    const sourceUrl = message.id
      ? `https://mail.google.com/mail/u/0/#inbox/${message.id}`
      : null;

    return {
      source_type: 'gmail',
      source_account: accountEmail,
      source_external_id: message.id!,
      source_url: sourceUrl,
      sender_id: senderEmail,
      sender_name: senderName,
      sender_email: senderEmail,
      title: subject,
      body: body || message.snippet || '',
      metadata: {
        thread_id: message.threadId,
        to,
        snippet: message.snippet,
      },
      attachments,
      raw_payload: { message_id: message.id, thread_id: message.threadId },
      received_at: receivedAt,
    };
  }

  private extractBody(
    payload?: {
      mimeType?: string | null;
      body?: { data?: string | null };
      parts?: Array<{ mimeType?: string | null; body?: { data?: string | null }; parts?: unknown[] }>;
    },
  ): string {
    if (!payload) return '';
    if (payload.mimeType === 'text/plain' && payload.body?.data) {
      return Buffer.from(payload.body.data, 'base64url').toString('utf8');
    }
    if (payload.parts) {
      for (const part of payload.parts) {
        if (part.mimeType === 'text/plain' && part.body?.data) {
          return Buffer.from(part.body.data, 'base64url').toString('utf8');
        }
      }
      for (const part of payload.parts) {
        const nested = this.extractBody(part as typeof payload);
        if (nested) return nested;
      }
    }
    return '';
  }

  private extractAttachments(
    payload?: {
      parts?: Array<{
        mimeType?: string | null;
        filename?: string | null;
        body?: { size?: number | null; attachmentId?: string | null };
      }>;
    },
  ): AttachmentMeta[] {
    const attachments: AttachmentMeta[] = [];
    if (!payload?.parts) return attachments;

    for (const part of payload.parts) {
      if (part.filename && part.body?.attachmentId) {
        attachments.push({
          filename: part.filename,
          mimeType: part.mimeType ?? undefined,
          size: part.body.size ?? undefined,
          sourceRef: part.body.attachmentId,
        });
      }
    }
    return attachments;
  }
}

export const gmailService = new GmailService();

let pollTimer: ReturnType<typeof setInterval> | null = null;

export function startGmailPoller(): void {
  if (!config.gmail.enabled() || pollTimer) return;
  console.log('[gmail] poller started');
  pollTimer = setInterval(() => {
    gmailService.poll().catch((err) => {
      console.error('[gmail] poll error:', err instanceof Error ? err.message : err);
    });
  }, config.gmail.pollIntervalMs);
}

export function stopGmailPoller(): void {
  if (pollTimer) {
    clearInterval(pollTimer);
    pollTimer = null;
    console.log('[gmail] poller stopped');
  }
}
