import { Injectable, Logger } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs';

const POD_HEADERS = {
  'Content-Type': 'application/json;charset=UTF-8',
  Origin: 'https://inkpod.vip',
  Referer: 'https://inkpod.vip/',
} as const;

/**
 * Minimum number of category nodes a healthy `category/tree/3` response contains.
 * Below this the response is treated as degenerate and rejected so the caller
 * never runs a destructive sync against a partial tree.
 */
export const MIN_SDS_CATEGORY_NODES = 10;

export interface SdsCategoryTreeNode {
  id: number | string | null;
  name?: string;
  icon?: string;
  children?: SdsCategoryTreeNode[];
  [key: string]: unknown;
}

export interface SdsProduct {
  id: number | string;
  name?: string;
  title?: string;
  pic?: string;
  image?: string;
  psd_img_url?: string;
  thumbImgUrl?: string;
  blankDesignUrl?: string;
  img_url?: string;
  show_img?: string;
  price?: number | string;
  currentPrice?: number | string;
  categoryId?: number | string;
  [key: string]: unknown;
}

export interface SdsProductsPage {
  items?: SdsProduct[];
  content?: SdsProduct[];
  totalCount?: number;
  totalElements?: number;
  total?: number;
  page?: number;
  size?: number;
  [key: string]: unknown;
}

export interface SdsProductVariant extends Record<string, unknown> {
  id?: number | string;
  sku?: string;
  size?: string;
  sizeId?: number | string;
  sizeDto?: { id?: number | string; sizeName?: string };
  colorId?: number | string;
  color_name?: string;
  color?: {
    colorId?: number | string;
    color?: string;
    color_name?: string;
    chineseName?: string;
  };
  currentPrice?: number | string;
  originalPrice?: number | string;
  unit_price?: number | string;
  min_price?: number | string;
  weight?: number | string;
  box_length?: number | string;
  box_width?: number | string;
  box_height?: number | string;
  status?: number | string;
  delFlag?: number | string;
  size_sort?: number | string;
  attribute_sort?: string;
  psd_img_url?: string;
  img_url?: string;
  blankDesignUrl?: string;
  designPrototype?: {
    detailImgUrls?: Array<{ imageUrl?: string }>;
    prototypeResultGroups?: Array<{ resultImage?: string }>;
    [key: string]: unknown;
  };
}

export interface SdsProductDetail extends Record<string, unknown> {
  id: number | string;
  name?: string;
  sku?: string;
  english_name?: string;
  blankDesignUrl?: string;
  detailsPageVideoUrl?: string;
  productionCycle?: number | string;
  minWeight?: number | string;
  min_price?: number | string;
  updateTime?: number | string;
  psd_img_url?: string;
  img_url?: string;
  texture?: { name?: string };
  product_details?: {
    reminder?: string;
    production_process?: string;
    material_description?: string;
    product_performance?: string;
    applicable_scenarios?: string;
    washing_instructions?: string;
    special_description?: string;
    design_explanation?: string;
    design_area?: string;
    picture_request?: string;
    product_size?: string;
    packaging_specification?: string;
  };
  subproducts?: {
    attributers?: Array<{
      size?: string;
      sizeId?: number | string;
      colors?: Array<{
        colorId?: number | string;
        color?: string;
        color_name?: string;
        chineseName?: string;
        colorSort?: number | string;
      }>;
    }>;
    items?: SdsProductVariant[];
  };
}

/**
 * Thin wrapper around the SDS (mapi.sdspod.com) endpoints that the
 * `SyncService` consumes.
 *
 * Exposed as its own service so it can be mocked cleanly in unit tests.
 */
@Injectable()
export class SdsClientService {
  private readonly logger = new Logger(SdsClientService.name);
  private readonly baseUrl: string;

  constructor(
    private readonly http: HttpService,
    config: ConfigService,
  ) {
    this.baseUrl =
      config.get<string>('SDS_API_BASE')?.replace(/\/$/, '') ??
      'https://mapi.sdspod.com';
  }

  private async request<T>(
    method: 'post' | 'get',
    url: string,
    body?: unknown,
    params?: Record<string, unknown>,
  ): Promise<T> {
    const MAX_RETRIES = 3;
    let lastError: unknown;
    for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
      try {
        const config = {
          headers: POD_HEADERS,
          timeout: 30000,
          ...(params ? { params } : {}),
        };
        const obs =
          method === 'post'
            ? this.http.post<T>(url, body, config)
            : this.http.get<T>(url, config);
        const { data } = await firstValueFrom(obs);
        return data as T;
      } catch (err) {
        lastError = err;
        const msg = err instanceof Error ? err.message : String(err);
        if (attempt < MAX_RETRIES) {
          this.logger.warn(`SDS request attempt ${attempt}/${MAX_RETRIES} failed: ${msg}`);
          await new Promise((r) => setTimeout(r, 1000 * attempt));
        }
      }
    }
    throw lastError;
  }

  async fetchCategoryTree(): Promise<SdsCategoryTreeNode[]> {
    const url = `${this.baseUrl}/category/tree/3`;
    const body = {
      withActivityArea: true,
      withPrivate: true,
      onlyHaveProduct: true,
    };
    const data = await this.request<unknown>('post', url, body);
    if (!Array.isArray(data)) {
      throw new Error(`SDS category tree returned ${typeof data}, expected array`);
    }
    if (data.length < MIN_SDS_CATEGORY_NODES) {
      throw new Error(
        `SDS category tree is degenerate (${data.length} nodes < ${MIN_SDS_CATEGORY_NODES}) — ` +
          `aborting to avoid destructive sync`,
      );
    }
    return data as SdsCategoryTreeNode[];
  }

  async fetchProductsPage(
    categoryId: string | number,
    page = 1,
    size = 50,
  ): Promise<SdsProductsPage> {
    const url = `${this.baseUrl}/products/page`;
    const data = await this.request<unknown>('get', url, undefined, { categoryId, page, size });
    if (!data || typeof data !== 'object') {
      throw new Error(`SDS products page returned ${typeof data}, expected object`);
    }
    return data as SdsProductsPage;
  }

  async fetchProductDetail(goodId: string | number): Promise<SdsProductDetail> {
    const url = `${this.baseUrl}/products/${encodeURIComponent(String(goodId))}`;
    const data = await this.request<unknown>('get', url);
    if (!data || typeof data !== 'object' || Array.isArray(data)) {
      throw new Error(`SDS product detail returned ${typeof data}, expected object`);
    }
    const detail = data as SdsProductDetail;
    if (String(detail.id) !== String(goodId)) {
      throw new Error(`SDS product detail id mismatch: expected ${goodId}, got ${String(detail.id)}`);
    }
    return detail;
  }
}
