import { Injectable } from '@nestjs/common';

/**
 * Marcadeo API client (the tracking backbone).
 *
 * This Shopify app is the Shopify-integration layer; Marcadeo (the affiliate/CPA
 * tracking platform) is the source of truth for campaigns, affiliates/publishers,
 * conversions and finance. Calls hit Marcadeo's REST API under /api/shopify/*
 * (Laravel Sanctum bearer token). Field names are snake_case to match Marcadeo.
 *
 * Configure via MARCADEO_BASE_URL + MARCADEO_API_TOKEN in .env. When absent the
 * service is a no-op (configured() === false) so nothing breaks pre-integration.
 */
@Injectable()
export class MarcadeoService {
  private get baseUrl() {
    return (process.env.MARCADEO_BASE_URL || '').replace(/\/$/, '');
  }
  private get token() {
    return process.env.MARCADEO_API_TOKEN || '';
  }

  configured() {
    return Boolean(this.baseUrl && this.token);
  }

  private async req<T = any>(path: string, init?: RequestInit): Promise<T> {
    const res = await fetch(`${this.baseUrl}/api/${path.replace(/^\//, '')}`, {
      ...init,
      headers: {
        Authorization: `Bearer ${this.token}`,
        'Content-Type': 'application/json',
        Accept: 'application/json',
        ...(init?.headers || {}),
      },
    });
    const body = (await res.json().catch(() => null)) as any;
    if (!res.ok) {
      const msg = body?.message || body?.error || `Marcadeo API ${res.status}`;
      throw new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
    }
    return body as T;
  }

  private post<T = any>(path: string, body: Record<string, unknown>) {
    return this.req<T>(path, { method: 'POST', body: JSON.stringify(body) });
  }

  /**
   * POST to a public web route (NOT under /api, no Sanctum) — used for Marcadeo's
   * /track conversion endpoint, which is the same endpoint every Marcadeo
   * integration posts conversions to. Reusing it means the conversion references
   * a real click (satisfying Marcadeo's conversion FK trigger).
   */
  private async web<T = any>(path: string, body: Record<string, unknown>): Promise<T> {
    const res = await fetch(`${this.baseUrl}/${path.replace(/^\//, '')}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify(body),
    });
    const out = (await res.json().catch(() => null)) as any;
    if (!res.ok) {
      const msg = out?.message || out?.error || `Marcadeo /${path} ${res.status}`;
      throw new Error(typeof msg === 'string' ? msg : JSON.stringify(msg));
    }
    return out as T;
  }

  /** Connectivity check — GET /api/user (Sanctum). */
  async ping(): Promise<{ ok: boolean; user?: unknown; error?: string }> {
    if (!this.configured()) return { ok: false, error: 'Marcadeo not configured' };
    try {
      const user = await this.req('user');
      return { ok: true, user };
    } catch (e) {
      return { ok: false, error: (e as Error).message };
    }
  }

  /** Register a store install → advertiser + default campaign. POST shopify/install */
  async install(input: {
    shopDomain: string;
    shopName?: string;
    email?: string;
    currency?: string;
    country?: string;
    accessToken?: string;
  }): Promise<{ storeId: number; advertiserId: number; campaignId: number }> {
    const d = await this.post('shopify/install', {
      shop_domain: input.shopDomain,
      shop_name: input.shopName,
      email: input.email,
      currency: input.currency,
      country: input.country,
      access_token: input.accessToken,
    });
    return { storeId: d.store_id, advertiserId: d.advertiser_id, campaignId: d.campaign_id };
  }

  /** S2S IP whitelist for an advertiser (Marcadeo /track enforces it natively). */
  async listIpWhitelist(advertiserId: number | string): Promise<{ ips: Array<{ id: number; ip_address: string; type: string; is_active: boolean }> }> {
    const d = await this.req(`shopify/ip-whitelist?advertiser_id=${advertiserId}`);
    return { ips: d.ips || [] };
  }
  async addIpWhitelist(advertiserId: number | string, ip: string, type?: 'SINGLE' | 'CIDR'): Promise<{ id: number }> {
    const d = await this.post('shopify/ip-whitelist', { advertiser_id: advertiserId, ip_address: ip, type });
    return { id: d.id };
  }
  async removeIpWhitelist(advertiserId: number | string, id: number | string): Promise<{ ok: boolean }> {
    const d = await this.req('shopify/ip-whitelist', {
      method: 'DELETE',
      body: JSON.stringify({ advertiser_id: advertiserId, id }),
    });
    return { ok: !!d.ok };
  }

  /** Update an advertiser's profile (name, …). POST shopify/advertiser/update */
  async updateAdvertiser(input: { advertiserId: number | string; name?: string }): Promise<{ ok: boolean }> {
    const d = await this.post('shopify/advertiser/update', {
      advertiser_id: input.advertiserId,
      name: input.name,
    });
    return { ok: !!d.ok };
  }

  /** Create a campaign/brand for an advertiser. POST shopify/campaigns */
  async createCampaign(input: {
    advertiserId: number | string;
    name: string;
    payoutType?: 'percent' | 'flat';
    payoutValue?: number;
    currency?: string;
  }): Promise<{ campaignId: number; goalId: number }> {
    const d = await this.post('shopify/campaigns', {
      advertiser_id: input.advertiserId,
      name: input.name,
      payout_type: input.payoutType ?? 'percent',
      payout_value: input.payoutValue ?? 0,
      currency: input.currency,
    });
    return { campaignId: d.campaign_id, goalId: d.goal_id };
  }

  /** Add a goal (conversion event) to a campaign. POST shopify/goals */
  async createGoal(input: {
    campaignId: number | string;
    name: string;
    goalModel?: 'CPS' | 'CPA' | 'CPL' | 'CPI' | 'CPM';
    payoutType?: 'percent' | 'flat';
    payoutValue?: number;
    isPrimary?: boolean;
    currency?: string;
  }): Promise<{ goalId: number }> {
    const d = await this.post('shopify/goals', {
      campaign_id: input.campaignId,
      name: input.name,
      goal_model: input.goalModel ?? 'CPS',
      payout_type: input.payoutType ?? 'percent',
      payout_value: input.payoutValue ?? 0,
      is_primary: input.isPrimary ?? false,
      currency: input.currency,
    });
    return { goalId: d.goal_id };
  }

  /** Create an affiliate/publisher. POST shopify/publishers */
  async createPublisher(input: {
    name: string;
    email: string;
    externalRef?: string;
  }): Promise<{ publisherId: number; prefix: string }> {
    const d = await this.post('shopify/publishers', {
      name: input.name,
      email: input.email,
      external_ref: input.externalRef,
    });
    return { publisherId: d.publisher_id, prefix: d.prefix };
  }

  /** Generate a tracking link. POST shopify/links. `destination` → deep link. */
  async createTrackingLink(input: {
    campaignId: number | string;
    publisherId: number | string;
    subId?: string;
    destination?: string;
  }): Promise<{ url: string; linkId?: number }> {
    const d = await this.post('shopify/links', {
      campaign_id: input.campaignId,
      publisher_id: input.publisherId,
      sub_id: input.subId,
      destination: input.destination,
    });
    return { url: d.url, linkId: d.link_id };
  }

  /** Map a coupon → affiliate on a campaign. POST shopify/coupons */
  async attachCoupon(input: {
    campaignId: number | string;
    userId: number | string; // the affiliate (publisher_id)
    couponCode: string;
    shopifyDiscountId?: string;
  }): Promise<{ couponId: number }> {
    const d = await this.post('shopify/coupons', {
      campaign_id: input.campaignId,
      user_id: input.userId,
      coupon_code: input.couponCode,
      shopify_discount_id: input.shopifyDiscountId,
    });
    return { couponId: d.coupon_id };
  }

  /** Set a per-affiliate custom payout on a campaign. POST shopify/publisher-payout */
  async setPublisherPayout(input: {
    campaignId: number | string;
    userId: number | string; // the affiliate (publisher_id)
    payoutType: 'percent' | 'flat';
    payoutValue: number;
  }): Promise<{ payoutId?: number; ok?: boolean }> {
    const d = await this.post('shopify/publisher-payout', {
      campaign_id: input.campaignId,
      user_id: input.userId,
      payout_type: input.payoutType,
      payout_value: input.payoutValue,
    });
    return { payoutId: d.payout_id, ok: d.ok };
  }

  /**
   * Record a conversion via Marcadeo's real /track endpoint. Requires the
   * click_id captured when the shopper arrived through the affiliate's /click
   * link (Marcadeo generated it and passed it to the storefront). The conversion
   * references that click, so Marcadeo's FK trigger is satisfied — no raw insert.
   *
   * `goalId` is the campaign's Sale goal (Campaign.marcadeoGoalId).
   */
  async postConversion(input: {
    clickId: string;
    goalId?: number | string;
    orderId: string;
    amount: number;
    status?: string; // e.g. 'approved' | 'pending'
  }): Promise<{ status: string; raw?: unknown }> {
    const d = await this.web('track', {
      click_id: input.clickId,
      goal_id: input.goalId,
      sale_amount: input.amount,
      order_id: input.orderId,
      status: input.status ?? 'pending',
    });
    return { status: d?.status ?? 'ok', raw: d };
  }

  /** Reverse conversions for a refunded order. POST shopify/conversions/reverse */
  async reverseConversion(input: { orderId: string; campaignId?: number | string }): Promise<{ count: number }> {
    const d = await this.post('shopify/conversions/reverse', {
      order_id: input.orderId,
      campaign_id: input.campaignId,
    });
    return { count: Number(d.count ?? 0) };
  }

  /** Pull conversion stats. GET shopify/reports */
  async getStats(input: {
    campaignId?: number | string;
    affiliateId?: number | string;
    from?: string;
    to?: string;
  }): Promise<{ conversions: number; revenue: number; payout: number }> {
    const params: Record<string, string> = {};
    if (input.campaignId != null) params.campaign_id = String(input.campaignId);
    if (input.affiliateId != null) params.affiliate_id = String(input.affiliateId);
    if (input.from) params.from = input.from;
    if (input.to) params.to = input.to;
    const q = new URLSearchParams(params).toString();
    const d = await this.req(`shopify/reports${q ? `?${q}` : ''}`);
    return {
      conversions: Number(d.conversions ?? 0),
      revenue: Number(d.revenue ?? 0),
      payout: Number(d.payout ?? 0),
    };
  }
}
