import type { Customer } from '../billing/middleware.js';
import type { OAuthCredentials } from '../multitenancy/credential-store.js';
import { createToolAudit } from '../multitenancy/audit-log.js';

const SLACK_API_BASE = 'https://slack.com/api';

interface SlackCredentials extends OAuthCredentials {
  channelId?: string;
}

function getEnvToken(account: string): string {
  const envKey = `SLACK_${account.toUpperCase()}_BOT_TOKEN`;
  return process.env[envKey] ?? '';
}

async function resolveCredentials(
  args: { account?: string },
  customer?: Customer
): Promise<{ token: string; defaultChannelId?: string }> {
  if (customer) {
    const creds = await customer.getCredential<SlackCredentials>('slack');
    if (!creds) throw new Error('Slack not connected for this account');
    return { token: creds.accessToken, defaultChannelId: creds.channelId };
  }
  const token = getEnvToken(args.account ?? 'default');
  if (!token) throw new Error('Missing Slack credentials. Set SLACK_{ACCOUNT}_BOT_TOKEN');
  return { token };
}

async function slackRequest(token: string, method: string, body?: Record<string, unknown>) {
  const res = await fetch(`${SLACK_API_BASE}/${method}`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
    signal: AbortSignal.timeout(15000),
  });

  if (!res.ok) {
    throw new Error(`Slack HTTP error (${res.status})`);
  }

  const data = await res.json() as { ok: boolean; error?: string; [key: string]: unknown };
  if (!data.ok) {
    throw new Error(`Slack API error: ${data.error}`);
  }

  return data;
}

export async function getMe(
  args: { account?: string },
  customer?: Customer
): Promise<{ user_id: string; user: string; team: string; team_id: string }> {
  const { token } = await resolveCredentials(args, customer);
  const data = await slackRequest(token, 'auth.test');
  return {
    user_id: data.user_id as string,
    user: data.user as string,
    team: data.team as string,
    team_id: data.team_id as string,
  };
}

export async function getChannels(
  args: { limit?: number; account?: string },
  customer?: Customer
): Promise<Array<{ id: string; name: string; is_member: boolean; num_members: number }>> {
  const { token } = await resolveCredentials(args, customer);
  const data = await slackRequest(token, 'conversations.list', {
    limit: args.limit ?? 100,
    types: 'public_channel,private_channel',
    exclude_archived: true,
  });
  const channels = data.channels as Array<{ id: string; name: string; is_member: boolean; num_members: number }>;
  return channels.map(c => ({ id: c.id, name: c.name, is_member: c.is_member, num_members: c.num_members }));
}

export async function sendMessage(
  args: { channel_id?: string; text: string; account?: string },
  customer?: Customer
): Promise<{ ts: string; channel: string }> {
  const { token, defaultChannelId } = await resolveCredentials(args, customer);
  const channel = args.channel_id ?? defaultChannelId;
  if (!channel) throw new Error('channel_id is required (or set a default channel when connecting)');

  const audit = customer ? createToolAudit(customer.id, 'slack:sendMessage') : null;
  const auditArgs = { channel };

  try {
    const data = await slackRequest(token, 'chat.postMessage', { channel, text: args.text });
    if (audit) await audit.success(auditArgs);
    return { ts: data.ts as string, channel: data.channel as string };
  } catch (err) {
    if (audit) await audit.error(auditArgs, String(err));
    throw err;
  }
}

export async function getMessages(
  args: { channel_id: string; limit?: number; account?: string },
  customer?: Customer
): Promise<Array<{ ts: string; text: string; user: string }>> {
  const { token } = await resolveCredentials(args, customer);
  const data = await slackRequest(token, 'conversations.history', {
    channel: args.channel_id,
    limit: args.limit ?? 10,
  });
  const messages = data.messages as Array<{ ts: string; text: string; user: string }>;
  return messages.map(m => ({ ts: m.ts, text: m.text, user: m.user }));
}
