import { env } from '../config/env';
import { logger } from '../utils/logger';

// --- Types ---

export interface ListmonkList {
  id: number;
  uuid: string;
  name: string;
  type: 'public' | 'private' | 'temporary';
  optin: 'single' | 'double';
  tags: string[];
  subscriber_count: number;
  created_at: string;
  updated_at: string;
}

export interface ListmonkSubscriber {
  id: number;
  uuid: string;
  email: string;
  name: string;
  status: 'enabled' | 'disabled' | 'blocklisted';
  lists: ListmonkList[];
  attribs: Record<string, unknown>;
  created_at: string;
  updated_at: string;
}

export interface BulkSyncResult {
  total: number;
  success: number;
  failed: number;
  errors: string[];
}

// --- Client ---

class ListmonkClient {
  private get baseUrl(): string {
    return env.LISTMONK_URL;
  }

  private get authHeader(): string {
    return 'Basic ' + Buffer.from(`${env.LISTMONK_ADMIN_USER}:${env.LISTMONK_ADMIN_PASSWORD}`).toString('base64');
  }

  private get enabled(): boolean {
    return env.LISTMONK_SYNC_ENABLED === 'true';
  }

  private assertEnabled(): void {
    if (!this.enabled) {
      throw new Error('Listmonk sync is disabled. Set LISTMONK_SYNC_ENABLED=true to enable.');
    }
  }

  private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
    const url = `${this.baseUrl}${path}`;
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 5000);

    try {
      const res = await fetch(url, {
        method,
        headers: {
          'Authorization': this.authHeader,
          'Content-Type': 'application/json',
        },
        body: body ? JSON.stringify(body) : undefined,
        signal: controller.signal,
      });

      if (!res.ok) {
        const text = await res.text().catch(() => '');
        throw new Error(`Listmonk API ${method} ${path} returned ${res.status}: ${text}`);
      }

      return await res.json() as T;
    } finally {
      clearTimeout(timeout);
    }
  }

  async checkHealth(): Promise<boolean> {
    try {
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 5000);
      try {
        const res = await fetch(`${this.baseUrl}/api/health`, {
          headers: { 'Authorization': this.authHeader },
          signal: controller.signal,
        });
        return res.ok;
      } finally {
        clearTimeout(timeout);
      }
    } catch (err) {
      logger.warn('Listmonk health check failed:', err instanceof Error ? err.message : err);
      return false;
    }
  }

  async getLists(): Promise<ListmonkList[]> {
    this.assertEnabled();
    try {
      const res = await this.request<{ data: { results: ListmonkList[] } }>('GET', '/api/lists?per_page=all');
      return res.data.results || [];
    } catch (err) {
      logger.warn('Failed to fetch Listmonk lists:', err instanceof Error ? err.message : err);
      throw err;
    }
  }

  async createList(name: string, type: 'public' | 'private', tags: string[]): Promise<ListmonkList> {
    this.assertEnabled();
    try {
      const res = await this.request<{ data: ListmonkList }>('POST', '/api/lists', {
        name,
        type,
        optin: 'single',
        tags,
      });
      return res.data;
    } catch (err) {
      logger.warn(`Failed to create Listmonk list "${name}":`, err instanceof Error ? err.message : err);
      throw err;
    }
  }

  async findSubscriberByEmail(email: string): Promise<ListmonkSubscriber | null> {
    this.assertEnabled();
    try {
      // Validate email format and sanitize for Listmonk query language
      // Strip all characters except valid email chars to prevent query injection
      const safeEmail = email.replace(/[^a-zA-Z0-9@._+\-]/g, '').replace(/'/g, "''");
      const query = encodeURIComponent(`subscribers.email='${safeEmail}'`);
      const res = await this.request<{ data: { results: ListmonkSubscriber[] } }>(
        'GET',
        `/api/subscribers?query=${query}&per_page=1`,
      );
      const results = res.data.results;
      return results && results.length > 0 ? results[0] : null;
    } catch (err) {
      logger.warn(`Failed to find subscriber ${email}:`, err instanceof Error ? err.message : err);
      return null;
    }
  }

  async createSubscriber(
    email: string,
    name: string,
    listIds: number[],
    attribs: Record<string, unknown>,
  ): Promise<ListmonkSubscriber> {
    this.assertEnabled();
    const res = await this.request<{ data: ListmonkSubscriber }>('POST', '/api/subscribers', {
      email,
      name,
      status: 'enabled',
      lists: listIds,
      attribs,
    });
    return res.data;
  }

  async updateSubscriber(
    id: number,
    data: { email: string; name?: string; lists?: number[]; attribs?: Record<string, unknown> },
  ): Promise<ListmonkSubscriber> {
    this.assertEnabled();
    const res = await this.request<{ data: ListmonkSubscriber }>('PUT', `/api/subscribers/${id}`, data);
    return res.data;
  }

  async updateList(
    id: number,
    data: { name?: string; tags?: string[] },
  ): Promise<ListmonkList> {
    this.assertEnabled();
    const res = await this.request<{ data: ListmonkList }>('PUT', `/api/lists/${id}`, data);
    return res.data;
  }

  async deleteList(id: number): Promise<void> {
    this.assertEnabled();
    await this.request<unknown>('DELETE', `/api/lists/${id}`);
  }

  async removeSubscriberFromLists(
    subscriberId: number,
    listIdsToRemove: number[],
    currentEmail: string,
    currentListIds: number[],
  ): Promise<ListmonkSubscriber> {
    this.assertEnabled();
    const filteredIds = currentListIds.filter(id => !listIdsToRemove.includes(id));
    return this.updateSubscriber(subscriberId, {
      email: currentEmail,
      lists: filteredIds,
    });
  }

  /**
   * Get campaigns with stats (for dashboard)
   */
  async getCampaigns(): Promise<Array<{
    id: number;
    name: string;
    status: string;
    sent: number;
    views: number;
    clicks: number;
    started_at: string | null;
    updated_at: string;
  }>> {
    try {
      const res = await this.request<{ data: { results: Array<{
        id: number;
        name: string;
        status: string;
        stats: { sent: number; views: number; clicks: number };
        started_at: string | null;
        updated_at: string;
      }> } }>('GET', '/api/campaigns?per_page=all&order_by=updated_at&order=desc');
      return (res.data.results || []).map(c => ({
        id: c.id,
        name: c.name,
        status: c.status,
        sent: c.stats?.sent || 0,
        views: c.stats?.views || 0,
        clicks: c.stats?.clicks || 0,
        started_at: c.started_at,
        updated_at: c.updated_at,
      }));
    } catch (err) {
      logger.warn('Failed to fetch Listmonk campaigns:', err instanceof Error ? err.message : err);
      return [];
    }
  }

  async upsertSubscriber(
    email: string,
    name: string,
    listIds: number[],
    attribs: Record<string, unknown>,
  ): Promise<ListmonkSubscriber> {
    this.assertEnabled();
    const existing = await this.findSubscriberByEmail(email);
    if (existing) {
      // Merge lists: combine existing + new, deduplicate
      const existingListIds = existing.lists.map(l => l.id);
      const mergedListIds = [...new Set([...existingListIds, ...listIds])];
      // Merge attribs
      const mergedAttribs = { ...existing.attribs, ...attribs };
      return this.updateSubscriber(existing.id, {
        email,
        name: name || existing.name,
        lists: mergedListIds,
        attribs: mergedAttribs,
      });
    }
    return this.createSubscriber(email, name, listIds, attribs);
  }
}

export const listmonkClient = new ListmonkClient();
