/**
 * JSON:API Client - Two-Layer Architecture
 *
 * This module implements a two-layer approach for JSON:API operations:
 *
 * Layer 1 (Generic): Pure JSON:API operations that work with any JSON:API server
 * - jsonApi.get(), jsonApi.list(), jsonApi.create(), etc.
 * - Could be extracted to @activepieces/pieces-common for reuse across pieces
 * - Handles raw JSON:API requests, URLs, and response formats
 *
 * Layer 2 (Drupal-specific): Convenience functions for Drupal entity operations
 * - drupal.getEntity(), drupal.listEntities(), drupal.createEntity(), etc.
 * - Abstracts away entity types, bundles, and URL construction
 * - Provides clean developer experience with simple objects instead of JSON:API format
 */

import {
  HttpMethod,
  httpClient,
} from '@activepieces/pieces-common';
import {
  AppConnectionValueForAuthProperty,
} from '@activepieces/pieces-framework';
import { drupalAuth } from '../auth';

export type DrupalAuthType = AppConnectionValueForAuthProperty<typeof drupalAuth>;

export interface JsonApiResource {
  type: string;
  id?: string;
  attributes: Record<string, any>;
  relationships?: Record<string, any>;
}

export interface JsonApiResponse {
  data: JsonApiResource | JsonApiResource[];
  included?: JsonApiResource[];
  links?: Record<string, string | { href: string }>;
  meta?: Record<string, any>;
}

/**
 * Makes a JSON:API request with proper authentication
 */
export async function makeJsonApiRequest<T = JsonApiResponse>(
  auth: DrupalAuthType,
  endpoint: string,
  method: HttpMethod = HttpMethod.GET,
  body?: any
) {
  const { website_url, username, password } = auth.props;

  const headers: Record<string, string> = {
    'Accept': 'application/vnd.api+json',
  };

  if (username && password) {
    const basicAuth = Buffer.from(`${username}:${password}`).toString('base64');
    headers['Authorization'] = `Basic ${basicAuth}`;
  }

  if (body) {
    headers['Content-Type'] = 'application/vnd.api+json';
  }

  try {
    const response = await httpClient.sendRequest({
      method,
      url: endpoint,
      headers,
      body: body ? JSON.stringify(body) : undefined,
    });

    // Sanitize response body if it's a string containing JSON
    if (response.body && typeof response.body === 'string') {
      try {
        // Remove invalid control characters that violate JSON specification (RFC 8259 Section 7)
        // Workaround for Drupal bug: https://www.drupal.org/project/drupal/issues/3549107
        // TODO: Remove this when Drupal issue is fixed
        // eslint-disable-next-line no-control-regex
        const cleanedBody = response.body.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
        response.body = JSON.parse(cleanedBody);
      } catch (parseError) {
        console.warn('Failed to parse JSON response, returning raw body:', parseError);
        // Return response as-is if parsing fails
      }
    }

    return response;
  } catch (error) {
    console.error('JSON:API request failed:', { endpoint, method, error });
    throw error;
  }
}

/**
 * Builds JSON:API URL for entity operations
 */
export function getJsonApiUrl(
  baseUrl: string,
  entityType: string,
  bundle: string,
  uuid?: string
): string {
  const cleanBaseUrl = baseUrl.replace(/\/+$/, '');
  const resourceType = `${entityType}--${bundle}`;

  let url = `${cleanBaseUrl}/jsonapi/${entityType}/${resourceType}`;

  if (uuid) {
    url += `/${uuid}`;
  }

  return url;
}

/**
 * Converts simple object to JSON:API format
 */
export function toJsonApiFormat(
  entityType: string,
  bundle: string,
  data: Record<string, any>,
  id?: string
): JsonApiResponse {
  const resourceType = `${entityType}--${bundle}`;

  const attributes: Record<string, any> = {};
  const relationships: Record<string, any> = {};

  const isRelationship = (value: any) => value && typeof value === 'object' && 'data' in value;

  for (const [key, value] of Object.entries(data)) {
    if (isRelationship(value)) {
      relationships[key] = value;
    } else {
      attributes[key] = value;
    }
  }

  const resource: JsonApiResource = {
    type: resourceType,
    attributes,
  };

  if (id) {
    resource.id = id;
  }

  if (Object.keys(relationships).length > 0) {
    resource.relationships = relationships;
  }

  return { data: resource };
}

/**
 * Converts JSON:API response to simple object format
 */
export function fromJsonApiFormat(response: JsonApiResponse): any | any[] {
  if (!response.data) return null;

  try {
    if (Array.isArray(response.data)) {
      return response.data
        .filter(resource => resource != null)
        .map(convertJsonApiResource);
    } else {
      return convertJsonApiResource(response.data);
    }
  } catch (error) {
    // Return empty array instead of throwing, so pagination can continue
    return [];
  }
}

function convertJsonApiResource(resource: JsonApiResource) {
  if (!resource || typeof resource !== 'object') {
    throw new Error('Invalid resource: resource is not an object');
  }

  if (!resource.type) {
    throw new Error('Invalid resource: missing required "type" field');
  }

  const result: any = {
    id: resource.id,
    type: resource.type,
  };

  // Safely copy attributes
  if (resource.attributes && typeof resource.attributes === 'object') {
    try {
      Object.assign(result, resource.attributes);
    } catch (error) {
      throw new Error(`Failed to copy attributes: ${error}`);
    }
  }

  // Safely copy relationships
  if (resource.relationships && typeof resource.relationships === 'object') {
    try {
      for (const [key, value] of Object.entries(resource.relationships)) {
        result[key] = value;
      }
    } catch (error) {
      throw new Error(`Failed to copy relationships: ${error}`);
    }
  }

  return result;
}

/**
 * Builds query parameters for JSON:API requests
 */
function buildQueryParams(options: {
  filters?: Record<string, any>;
  sort?: string;
  sortDirection?: string;
  fields?: string[];
  resourceType?: string;
  limit?: number;
}): string {
  const params = new URLSearchParams();

  if (options.filters) {
    for (const [key, value] of Object.entries(options.filters)) {
      params.append(`filter[${key}]`, String(value));
    }
  }

  if (options.sort) {
    const sortParam = options.sortDirection === 'ASC'
      ? options.sort
      : `-${options.sort}`;
    params.append('sort', sortParam);
  }

  if (options.fields && options.resourceType) {
    const fieldsParam = options.fields.join(',');
    params.append(`fields[${options.resourceType}]`, fieldsParam);
  }

  if (options.limit && options.limit > 0) {
    params.append('page[limit]', String(options.limit));
  }

  const queryString = params.toString();
  return queryString ? `?${queryString}` : '';
}

// =============================================================================
// LAYER 1: Generic JSON:API Operations
//
// These functions work with any JSON:API server and handle the raw JSON:API
// specification. They could be extracted to @activepieces/pieces-common for
// reuse across different pieces (Rails API, Laravel API, etc.)
// =============================================================================

/**
 * Generic JSON:API client for any JSON:API compliant server
 */
export const jsonApi = {
  /**
   * Fetch a single resource by full JSON:API path
   * @example jsonApi.get(auth, '/jsonapi/node/node--article/12345')
   */
  async get(auth: DrupalAuthType, resourcePath: string) {
    const url = `${auth.props.website_url.replace(/\/+$/, '')}${resourcePath}`;
    const result = await makeJsonApiRequest(auth, url, HttpMethod.GET);

    if (result.status === 200) {
      return fromJsonApiFormat(result.body as JsonApiResponse);
    } else if (result.status === 404) {
      throw new Error(`Resource not found: ${resourcePath}`);
    } else if (result.status === 403) {
      throw new Error(`Access denied: ${resourcePath}`);
    }

    throw new Error(`Failed to get resource: ${result.status}`);
  },

  /**
   * Fetch a collection of resources with optional query parameters
   * Follows pagination links if present to retrieve all requested data
   * @example jsonApi.list(auth, '/jsonapi/node/node--article', { sort: 'created', filters: { status: '1' } })
   */
  async list(auth: DrupalAuthType, collectionPath: string, options?: {
    filters?: Record<string, any>;
    sort?: string;
    sortDirection?: string;
    fields?: string[];
    resourceType?: string;
    limit?: number;
  }) {
    const allEntities: any[] = [];
    const query = options ? buildQueryParams(options) : '';
    let url: string | null = `${auth.props.website_url.replace(/\/+$/, '')}${collectionPath}${query}`;

    do {
      const result = await makeJsonApiRequest(auth, url, HttpMethod.GET);

      if (result.status !== 200) {
        throw new Error(`Failed to list resources: ${result.status}`);
      }

      if (!result.body) {
        break;
      }

      // Parse JSON if response body is a string
      let parsedBody: JsonApiResponse;
      if (typeof result.body === 'string') {
        try {
          parsedBody = JSON.parse(result.body);
        } catch (parseError) {
          console.warn('Skipping page due to corrupted data in Drupal database:', parseError);
          url = null; // Stop pagination
          break;
        }
      } else {
        parsedBody = result.body as JsonApiResponse;
      }

      const response = parsedBody;
      const entities = fromJsonApiFormat(response);

      // Add entities from this page
      if (Array.isArray(entities)) {
        allEntities.push(...entities);
      } else if (entities) {
        allEntities.push(entities);
      }

      // Continue to next page if it exists
      const nextLink = response.links?.['next'];
      url = typeof nextLink === 'string' ? nextLink : nextLink?.href || null;

    } while (url);

    return allEntities;
  },

  /**
   * Create a new resource with JSON:API formatted data
   * @example jsonApi.create(auth, '/jsonapi/node/node--article', jsonApiFormattedData)
   */
  async create(auth: DrupalAuthType, collectionPath: string, jsonApiData: JsonApiResponse) {
    const url = `${auth.props.website_url.replace(/\/+$/, '')}${collectionPath}`;
    const result = await makeJsonApiRequest(auth, url, HttpMethod.POST, jsonApiData);

    if (result.status === 201 || result.status === 200) {
      return fromJsonApiFormat(result.body as JsonApiResponse);
    } else if (result.status === 422) {
      const errors = (result.body as any).errors || [];
      const errorMsg = errors.map((e: any) => e.detail || e.title).join(', ');
      throw new Error(`Validation failed: ${errorMsg}`);
    } else if (result.status === 403) {
      throw new Error('Permission denied to create resource');
    }

    throw new Error(`Failed to create resource: ${result.status}`);
  },

  /**
   * Update a resource with JSON:API formatted data
   * @example jsonApi.update(auth, '/jsonapi/node/node--article/12345', jsonApiFormattedData)
   */
  async update(auth: DrupalAuthType, resourcePath: string, jsonApiData: JsonApiResponse) {
    const url = `${auth.props.website_url.replace(/\/+$/, '')}${resourcePath}`;
    const result = await makeJsonApiRequest(auth, url, HttpMethod.PATCH, jsonApiData);

    if (result.status === 200) {
      return fromJsonApiFormat(result.body as JsonApiResponse);
    } else if (result.status === 422) {
      const errors = (result.body as any).errors || [];
      const errorMsg = errors.map((e: any) => e.detail || e.title).join(', ');
      throw new Error(`Validation failed: ${errorMsg}`);
    } else if (result.status === 404) {
      throw new Error(`Resource not found: ${resourcePath}`);
    } else if (result.status === 403) {
      throw new Error('Permission denied to update resource');
    }

    throw new Error(`Failed to update resource: ${result.status}`);
  },

  /**
   * Delete a resource
   * @example jsonApi.delete(auth, '/jsonapi/node/node--article/12345')
   */
  async delete(auth: DrupalAuthType, resourcePath: string) {
    const url = `${auth.props.website_url.replace(/\/+$/, '')}${resourcePath}`;
    const result = await makeJsonApiRequest(auth, url, HttpMethod.DELETE);

    if (result.status === 204 || result.status === 200) {
      return { success: true, message: `Deleted resource: ${resourcePath}` };
    } else if (result.status === 404) {
      throw new Error(`Resource not found: ${resourcePath}`);
    } else if (result.status === 403) {
      throw new Error('Permission denied to delete resource');
    }

    throw new Error(`Failed to delete resource: ${result.status}`);
  }
};

// =============================================================================
// LAYER 2: Drupal-Specific Operations
//
// These functions provide a clean developer experience by abstracting away
// Drupal-specific concepts like entity types, bundles, and JSON:API formatting.
// They use the generic JSON:API layer internally.
// =============================================================================

/**
 * Drupal-specific entity operations with simplified API
 * Handles entity types, bundles, URL construction, and data format conversion
 */
export const drupal = {
  /**
   * Get a single Drupal entity by entity type, bundle, and UUID
   * @example drupal.getEntity(auth, 'node', 'article', '12345-uuid')
   */
  async getEntity(auth: DrupalAuthType, entityType: string, bundle: string, uuid: string) {
    const resourcePath = `/jsonapi/${entityType}/${bundle}/${uuid}`;
    return await jsonApi.get(auth, resourcePath);
  },

  /**
   * List Drupal entities with optional filtering, sorting, and field selection
   * @example drupal.listEntities(auth, 'node', 'article', { filters: { status: '1' }, sort: 'created' })
   */
  async listEntities(auth: DrupalAuthType, entityType: string, bundle: string, options?: {
    filters?: Record<string, any>;
    sort?: string;
    sortDirection?: string;
    fields?: string[];
    limit?: number;
  }) {
    const collectionPath = `/jsonapi/${entityType}/${bundle}`;
    const queryOptions = options ? {
      ...options,
      resourceType: `${entityType}--${bundle}`
    } : undefined;

    return await jsonApi.list(auth, collectionPath, queryOptions);
  },

  /**
   * Create a new Drupal entity with simple object data (automatically converts to JSON:API format)
   * @example drupal.createEntity(auth, 'node', 'article', { title: 'My Article', body: 'Content...' })
   */
  async createEntity(auth: DrupalAuthType, entityType: string, bundle: string, entityData: Record<string, any>) {
    const collectionPath = `/jsonapi/${entityType}/${bundle}`;
    const jsonApiData = toJsonApiFormat(entityType, bundle, entityData);

    return await jsonApi.create(auth, collectionPath, jsonApiData);
  },

  /**
   * Update a Drupal entity with simple object data (automatically converts to JSON:API format)
   * @example drupal.updateEntity(auth, 'node', 'article', '12345-uuid', { title: 'Updated Title' })
   */
  async updateEntity(auth: DrupalAuthType, entityType: string, bundle: string, uuid: string, entityData: Record<string, any>) {
    const resourcePath = `/jsonapi/${entityType}/${bundle}/${uuid}`;
    const jsonApiData = toJsonApiFormat(entityType, bundle, entityData, uuid);

    return await jsonApi.update(auth, resourcePath, jsonApiData);
  },

  /**
   * Delete a Drupal entity
   * @example drupal.deleteEntity(auth, 'node', 'article', '12345-uuid')
   */
  async deleteEntity(auth: DrupalAuthType, entityType: string, bundle: string, uuid: string) {
    const resourcePath = `/jsonapi/${entityType}/${bundle}/${uuid}`;
    return await jsonApi.delete(auth, resourcePath);
  }
};
