import type {
  IExecuteFunctions,
  ILoadOptionsFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';

import {
  DATA_PRODUCT_BASE_PATH,
  DATA_PRODUCT_HISTORY_SUFFIX,
  DATA_PRODUCT_READER_CATALOG_PATH,
  DATA_PRODUCT_SNAPSHOT_SUFFIX,
  DATA_PLANE_DEFAULT_BASE_URL,
  DATA_PLANE_BASE_URL_ENV,
  NDC_DATA_PRODUCT_READER_CREDENTIAL,
  NDC_NODE_ICON,
} from '../shared/constants';
import {
  historyReadParameters,
  normalizeHistoryResponse,
  normalizeSnapshotFacts,
  snapshotReadParameters,
} from '../shared/contracts';
import { safeHttpError, safeInputError } from '../shared/errors';
import { loadDataProductOptions, ndcRequest, serviceBaseUrl } from '../shared/http';

export class NdcDataProductRead implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'NDC Data Product Read',
    name: 'ndcDataProductRead',
    icon: NDC_NODE_ICON,
    group: ['input'],
    version: [1, 2],
    subtitle: '={{$parameter["mode"] || "snapshot"}} · {{$parameter["dataProductId"]}}',
    description: 'Read a scoped NDC Data Product snapshot or bounded history',
    defaults: { name: 'NDC Data Product Read' },
    inputs: [NodeConnectionTypes.Main],
    outputs: [NodeConnectionTypes.Main],
    credentials: [{ name: NDC_DATA_PRODUCT_READER_CREDENTIAL, required: true }],
    properties: [
      {
        displayName: 'Mode',
        name: 'mode',
        type: 'options',
        options: [
          { name: 'Current Snapshot', value: 'snapshot' },
          { name: 'History', value: 'history' },
        ],
        default: 'snapshot',
        required: true,
      },
      {
        displayName: 'Data Product Name or ID',
        name: 'dataProductId',
        type: 'options',
        typeOptions: { loadOptionsMethod: 'getDataProducts' },
        default: '',
        required: true,
      },
      {
        displayName: 'Page Size',
        name: 'pageSize',
        type: 'number',
        typeOptions: { minValue: 1, maxValue: 5000, numberStepSize: 1 },
        default: 1000,
        required: true,
        description: 'Maximum number of snapshot facts to return',
        displayOptions: { show: { mode: ['snapshot'] } },
      },
      {
        displayName: 'From',
        name: 'from',
        type: 'dateTime',
        default: '',
        required: true,
        displayOptions: { show: { mode: ['history'] } },
      },
      {
        displayName: 'To',
        name: 'to',
        type: 'dateTime',
        default: '',
        required: true,
        displayOptions: { show: { mode: ['history'] } },
      },
      {
        displayName: 'Resolution (MS)',
        name: 'resolutionMs',
        type: 'number',
        typeOptions: { minValue: 1000, maxValue: 86400000, numberStepSize: 1000 },
        default: 60000,
        required: true,
        description: 'Provider-neutral history bucket size; must be a multiple of the Data Product native history interval',
        displayOptions: { show: { mode: ['history'] } },
      },
      {
        displayName: 'Source IDs',
        name: 'sourceIds',
        type: 'string',
        default: '',
        description: 'Optional comma-separated entity IDs; empty reads all entities in the scoped Data Product',
        displayOptions: { show: { mode: ['history'] } },
      },
      {
        displayName: 'Limit',
        name: 'limit',
        type: 'number',
        typeOptions: { minValue: 1, maxValue: 5000, numberStepSize: 1 },
        default: 50,
        required: true,
        description: 'Max number of results to return',
        displayOptions: { show: { mode: ['history'] } },
      },
      {
        displayName: 'Cursor',
        name: 'cursor',
        type: 'string',
        default: '',
        description: 'Opaque continuation cursor returned by the previous history page',
        displayOptions: { show: { mode: ['history'] } },
      },
    ],
  };

  methods = {
    loadOptions: {
      async getDataProducts(this: ILoadOptionsFunctions) {
        return loadDataProductOptions(
          this,
          NDC_DATA_PRODUCT_READER_CREDENTIAL,
          DATA_PLANE_BASE_URL_ENV,
          DATA_PLANE_DEFAULT_BASE_URL,
          DATA_PRODUCT_READER_CATALOG_PATH,
        );
      },
    },
  };

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const mode = this.getNodeParameter('mode', 0, 'snapshot') as string;
    const dataProductId = this.getNodeParameter('dataProductId', 0) as string;
    if (mode === 'history') {
      let parameters;
      try {
        parameters = historyReadParameters({
          dataProductId,
          from: this.getNodeParameter('from', 0),
          to: this.getNodeParameter('to', 0),
          resolutionMs: this.getNodeParameter('resolutionMs', 0, 60000),
          sourceIds: this.getNodeParameter('sourceIds', 0, ''),
          limit: this.getNodeParameter('limit', 0, 1000),
          cursor: this.getNodeParameter('cursor', 0, ''),
        });
      } catch (error) {
        throw new NodeOperationError(this.getNode(), safeInputError(error, 'ndc_data_product_history_input_invalid'));
      }
      try {
        const baseUrl = serviceBaseUrl(DATA_PLANE_BASE_URL_ENV, DATA_PLANE_DEFAULT_BASE_URL);
        const response = await ndcRequest(this, NDC_DATA_PRODUCT_READER_CREDENTIAL, {
          method: 'GET',
          url: `${baseUrl}${DATA_PRODUCT_BASE_PATH}/${parameters.encodedProductId}${DATA_PRODUCT_HISTORY_SUFFIX}`,
          qs: {
            from: parameters.from,
            to: parameters.to,
            resolutionMs: parameters.resolutionMs,
            ...(parameters.sourceIds.length ? { sourceIds: parameters.sourceIds.join(',') } : {}),
            limit: parameters.limit,
            ...(parameters.cursor ? { cursor: parameters.cursor } : {}),
          },
        });
        return [[{ json: normalizeHistoryResponse(response), pairedItem: { item: 0 } }]];
      } catch (error) {
        const safe = error instanceof Error && error.message === 'data_product_history_invalid'
          ? safeInputError(error, 'ndc_data_product_history_invalid')
          : safeHttpError(error, 'ndc_data_product_history_read_failed');
        throw new NodeOperationError(this.getNode(), safe);
      }
    }
    if (mode !== 'snapshot') {
      throw new NodeOperationError(this.getNode(), safeInputError(new Error('read_mode_invalid'), 'ndc_data_product_read_input_invalid'));
    }
    const pageSize = this.getNodeParameter('pageSize', 0, 1000) as number;
    let parameters;
    try {
      parameters = snapshotReadParameters(dataProductId, pageSize);
    } catch (error) {
      throw new NodeOperationError(this.getNode(), safeInputError(error, 'ndc_data_product_read_input_invalid'));
    }

    try {
      const baseUrl = serviceBaseUrl(DATA_PLANE_BASE_URL_ENV, DATA_PLANE_DEFAULT_BASE_URL);
      const response = await ndcRequest(this, NDC_DATA_PRODUCT_READER_CREDENTIAL, {
        method: 'GET',
        url: `${baseUrl}${DATA_PRODUCT_BASE_PATH}/${parameters.encodedProductId}${DATA_PRODUCT_SNAPSHOT_SUFFIX}`,
        qs: {
          limit: parameters.pageSize,
        },
      });
      const facts = normalizeSnapshotFacts(response);
      return [facts.map((fact) => ({ json: fact, pairedItem: { item: 0 } }))];
    } catch (error) {
      const safe = error instanceof Error && error.message === 'data_product_snapshot_invalid'
        ? safeInputError(error, 'ndc_data_product_snapshot_invalid')
        : safeHttpError(error, 'ndc_data_product_read_failed');
      throw new NodeOperationError(this.getNode(), safe);
    }
  }
}
