import * as _ from 'lodash';
import {
  bruToJsonV2,
  jsonToBruV2,
  bruToEnvJsonV2,
  envJsonToBruV2,
  collectionBruToJson as _collectionBruToJson,
  jsonToCollectionBru as _jsonToCollectionBru
} from '@usebruno/lang';
import { getOauth2AdditionalParameters } from './utils/oauth2-additional-params';

export const parseBruRequest = (data: string | any, parsed: boolean = false): any => {
  try {
    const json = parsed ? data : bruToJsonV2(data);

    let requestType = _.get(json, 'meta.type');
    switch (requestType) {
      case 'http':
        requestType = 'http-request';
        break;
      case 'graphql':
        requestType = 'graphql-request';
        break;
      case 'grpc':
        requestType = 'grpc-request';
        break;
      case 'ws':
        requestType = 'ws-request';
        break;
      default:
        requestType = 'http-request';
    }

    const sequence = _.get(json, 'meta.seq');
    const tags = _.get(json, 'meta.tags', []);
    const urlPath: Record<typeof requestType, string> = {
      'grpc-request': 'grpc.url',
      'ws-request': 'ws.url',
      'default': 'http.url'
    };
    const transformedJson = {
      type: requestType,
      name: _.get(json, 'meta.name'),
      seq: !_.isNaN(sequence) ? Number(sequence) : 1,
      settings: _.get(json, 'settings', {}),
      tags: Array.isArray(tags) ? tags : [],
      request: {
        // Preserving special characters in custom methods. Using _.upperCase strips special characters.
        method:
          requestType === 'grpc-request'
            ? _.get(json, 'grpc.method', '')
            : String(_.get(json, 'http.method') ?? '').toUpperCase(),
        url: _.get(json, urlPath[requestType], _.get(json, urlPath.default)),
        headers: requestType === 'grpc-request' ? _.get(json, 'metadata', []) : _.get(json, 'headers', []),
        auth: _.get(json, 'auth', {}),
        body: _.get(json, 'body', {}),
        script: _.get(json, 'script', {}),
        vars: _.get(json, 'vars', {}),
        assertions: _.get(json, 'assertions', []),
        tests: _.get(json, 'tests', ''),
        docs: _.get(json, 'docs', '')
      },
      examples: _.get(json, 'examples', []).map((e: any) => {
        return bruExampleToJson(e, true, requestType, _.get(json, 'http.method'));
      })
    } as any;

    // Add request type specific fields
    if (requestType === 'grpc-request') {
      const selectedMethodType = _.get(json, 'grpc.methodType');
      selectedMethodType && ((transformedJson.request as any).methodType = selectedMethodType);
      const protoPath = _.get(json, 'grpc.protoPath');
      protoPath && ((transformedJson.request as any).protoPath = protoPath);
      transformedJson.request.auth.mode = _.get(json, 'grpc.auth', 'none');
      transformedJson.request.body = _.get(json, 'body', {
        mode: 'grpc',
        grpc: _.get(json, 'body.grpc', [
          {
            name: 'message 1',
            content: '{}'
          }
        ])
      });
    } else if (requestType === 'ws-request') {
      transformedJson.request.auth.mode = _.get(json, 'ws.auth', 'none');
      transformedJson.request.body = _.get(json, 'body', {
        mode: 'ws',
        ws: _.get(json, 'body.ws', [
          {
            name: 'message 1',
            content: '{}'
          }
        ])
      });
    } else {
      // For HTTP and GraphQL
      (transformedJson.request as any).params = _.get(json, 'params', []);
      transformedJson.request.auth.mode = _.get(json, 'http.auth', 'none');
      transformedJson.request.body.mode = _.get(json, 'http.body', 'none');
    }

    // add oauth2 additional parameters if they exist
    const hasOauth2GrantType = json?.auth?.oauth2?.grantType;
    if (hasOauth2GrantType) {
      const additionalParameters = getOauth2AdditionalParameters(json);
      const hasAdditionalParameters = Object.keys(additionalParameters || {}).length > 0;
      if (hasAdditionalParameters) {
        transformedJson.request.auth.oauth2.additionalParameters = additionalParameters;
      }
    }
    return transformedJson;
  } catch (error) {
    console.log('parseBruRequest error', error);
    throw error;
  }
};

export const stringifyBruRequest = (json: any): string => {
  try {
    let type = _.get(json, 'type');
    switch (type) {
      case 'http-request':
        type = 'http';
        break;
      case 'graphql-request':
        type = 'graphql';
        break;
      case 'grpc-request':
        type = 'grpc';
        break;
      case 'ws-request':
        type = 'ws';
        break;
      default:
        type = 'http';
    }

    const sequence = _.get(json, 'seq');

    // Start with the common meta section
    const bruJson = {
      meta: {
        name: _.get(json, 'name'),
        type: type,
        seq: !_.isNaN(sequence) ? Number(sequence) : 1,
        tags: _.get(json, 'tags', [])
      }
    } as any;

    // For HTTP and GraphQL requests, maintain the current structure
    if (type === 'http' || type === 'graphql') {
      bruJson.http = {
        // Preserve special characters in custom request methods. Avoid _.lowerCase which strips symbols.
        method: String(_.get(json, 'request.method') ?? '').toLowerCase(),
        url: _.get(json, 'request.url'),
        auth: _.get(json, 'request.auth.mode', 'none'),
        body: _.get(json, 'request.body.mode', 'none')
      };
      bruJson.params = _.get(json, 'request.params', []);
      bruJson.body = _.get(json, 'request.body', {
        mode: 'json',
        json: '{}'
      });
    } else if (type === 'grpc') {
      // For gRPC, add gRPC-specific structure but maintain field names
      bruJson.grpc = {
        url: _.get(json, 'request.url'),
        auth: _.get(json, 'request.auth.mode', 'none'),
        body: _.get(json, 'request.body.mode', 'grpc')
      };
      // Only add method if it exists
      const method = _.get(json, 'request.method');
      const methodType = _.get(json, 'request.methodType');
      const protoPath = _.get(json, 'request.protoPath');
      if (method) bruJson.grpc.method = method;
      if (methodType) bruJson.grpc.methodType = methodType;
      if (protoPath) bruJson.grpc.protoPath = protoPath;
      bruJson.body = _.get(json, 'request.body', {
        mode: 'grpc',
        grpc: _.get(json, 'request.body.grpc', [
          {
            name: 'message 1',
            content: '{}'
          }
        ])
      });
    } else if (type === 'ws') {
      bruJson.ws = {
        url: _.get(json, 'request.url'),
        auth: _.get(json, 'request.auth.mode', 'none'),
        body: _.get(json, 'request.body.mode', 'ws')
      };

      bruJson.body = _.get(json, 'request.body', {
        mode: 'ws',
        ws: _.get(json, 'request.body.ws', [
          {
            name: 'message 1',
            content: '{}'
          }
        ])
      });
    }

    // Common fields for all request types
    if (type === 'grpc') {
      bruJson.metadata = _.get(json, 'request.headers', []); // Use metadata for gRPC
    } else {
      bruJson.headers = _.get(json, 'request.headers', []); // Use headers for HTTP/GraphQL
    }
    bruJson.auth = _.get(json, 'request.auth', {});
    bruJson.script = _.get(json, 'request.script', {});
    bruJson.vars = {
      req: _.get(json, 'request.vars.req', []),
      res: _.get(json, 'request.vars.res', [])
    };
    // should we add assertions and tests for grpc requests?
    bruJson.assertions = _.get(json, 'request.assertions', []);
    bruJson.tests = _.get(json, 'request.tests', '');
    bruJson.settings = _.get(json, 'settings', {});
    bruJson.docs = _.get(json, 'request.docs', '');
    bruJson.examples = _.get(json, 'examples', []).map((e: any) => jsonExampleToBru(e));

    const bru = jsonToBruV2(bruJson);
    return bru;
  } catch (error) {
    throw error;
  }
};

export const parseBruCollection = (data: string | any, parsed: boolean = false): any => {
  try {
    const json = parsed ? data : _collectionBruToJson(data);

    const transformedJson: any = {
      request: {
        headers: _.get(json, 'headers', []),
        auth: _.get(json, 'auth', {}),
        script: _.get(json, 'script', {}),
        vars: _.get(json, 'vars', {}),
        tests: _.get(json, 'tests', '')
      },
      settings: _.get(json, 'settings', {}),
      docs: _.get(json, 'docs', '')
    };

    // add meta if it exists
    // this is only for folder bru file
    if (json.meta) {
      transformedJson.meta = {
        name: json.meta.name
      };

      // Include seq if it exists
      if (json.meta.seq !== undefined) {
        const sequence = json.meta.seq;
        transformedJson.meta.seq = !isNaN(sequence) ? Number(sequence) : 1;
      }
    }

    // add oauth2 additional parameters if they exist
    const hasOauth2GrantType = json?.auth?.oauth2?.grantType;
    if (hasOauth2GrantType) {
      const additionalParameters = getOauth2AdditionalParameters(json);
      const hasAdditionalParameters = Object.keys(additionalParameters).length > 0;
      if (hasAdditionalParameters) {
        transformedJson.request.auth.oauth2.additionalParameters = additionalParameters;
      }
    }

    return transformedJson;
  } catch (error) {
    return Promise.reject(error);
  }
};

export const stringifyBruCollection = (json: any, isFolder?: boolean): string => {
  try {
    const collectionBruJson: any = {
      headers: _.get(json, 'request.headers', []),
      script: {
        req: _.get(json, 'request.script.req', ''),
        res: _.get(json, 'request.script.res', '')
      },
      vars: {
        req: _.get(json, 'request.vars.req', []),
        res: _.get(json, 'request.vars.res', [])
      },
      tests: _.get(json, 'request.tests', ''),
      auth: _.get(json, 'request.auth', {}),
      docs: _.get(json, 'docs', '')
    };

    // add meta if it exists
    // this is only for folder bru file
    if (json?.meta) {
      collectionBruJson.meta = {
        name: json.meta.name
      };

      // Include seq if it exists
      if (json.meta.seq !== undefined) {
        const sequence = json.meta.seq;
        collectionBruJson.meta.seq = !isNaN(sequence) ? Number(sequence) : 1;
      }
    }

    if (!isFolder) {
      collectionBruJson.auth = _.get(json, 'request.auth', {});
    }

    return _jsonToCollectionBru(collectionBruJson);
  } catch (error) {
    throw error;
  }
};

export const parseBruEnvironment = (bru: string): any => {
  try {
    const json = bruToEnvJsonV2(bru);

    // the app env format requires each variable to have a type
    // this need to be evaluated and safely removed
    // i don't see it being used in schema validation
    if (json && json.variables && json.variables.length) {
      _.each(json.variables, (v: any) => (v.type = 'text'));
    }

    return json;
  } catch (error) {
    return Promise.reject(error);
  }
};

export const stringifyBruEnvironment = (json: any): string => {
  try {
    const bru = envJsonToBruV2(json);
    return bru;
  } catch (error) {
    throw error;
  }
};

// New functions for example handling
export const bruExampleToJson = (data: string | any, parsed: boolean = false, parentType?: string, parentMethod?: string): any => {
  try {
    const json = parsed ? data : bruToJsonV2(data);

    // Use parent request's type and method if provided
    const requestType = parentType || _.get(json, 'meta.type', 'http');
    const requestMethod = parentMethod || _.get(json, 'http.method', 'GET');

    let transformedType = requestType;
    switch (requestType) {
      case 'http':
        transformedType = 'http-request';
        break;
      case 'graphql':
        transformedType = 'graphql-request';
        break;
      case 'grpc':
        transformedType = 'grpc-request';
        break;
      case 'ws':
        transformedType = 'ws-request';
        break;
      default:
        transformedType = 'http-request';
    }

    /**
     * Backward compatibility (pre-v3.0.2 - v3.2.0): Postman imports before PR #6876 stored status/statusText swapped
     * (code: "OK", text: "202" instead of code: 202, text: "OK"). Detect and swap back.
     * TODO(Sid / Shubh): Remove after v5 — all collections should be migrated by then.
     */
    let status = _.get(json, 'response.status', '200');
    let statusText = _.get(json, 'response.statusText', 'OK');
    if (isNaN(Number(status)) && !isNaN(Number(statusText))) {
      [status, statusText] = [statusText, status];
    }

    // Follow the same structure as the main request, but with missing fields for examples
    const transformedJson = {
      type: transformedType,
      name: _.get(json, 'name'),
      description: _.get(json, 'description', ''),
      // Examples don't have seq, settings, tags
      request: {
        method: _.get(json, 'request.method') || requestMethod,
        url: _.get(json, 'request.url'),
        headers: _.get(json, 'request.headers', []),
        body: _.get(json, 'request.body', {
          mode: 'none'
        }),
        // Examples don't have script, vars, assertions, tests, docs
        params: _.get(json, 'request.params', [])
      },
      response: {
        headers: _.get(json, 'response.headers', []).map((header: any) => ({
          name: header.name,
          value: header.value
        })),
        status: Number(status) || 200,
        statusText: statusText || 'OK',
        body: {
          type: _.get(json, 'response.body.type', 'json'),
          content: _.get(json, 'response.body.content', '')
        }
      }
    } as any;

    return transformedJson;
  } catch (error) {
    console.log('bruExampleToJson error', error);
    throw error;
  }
};

export const jsonExampleToBru = (json: any) => {
  try {
    // Transform the JSON to match the same structure as main request, but with missing fields
    const exampleJson = {
      name: _.get(json, 'name'),
      description: _.get(json, 'description', ''),
      // Examples don't have seq, settings, tags
      request: {
        method: _.get(json, 'request.method'),
        url: _.get(json, 'request.url'),
        headers: _.get(json, 'request.headers', []),
        body: _.get(json, 'request.body', {}),
        // Examples don't have script, vars, assertions, tests, docs
        params: _.get(json, 'request.params', [])
      },
      response: _.get(json, 'response', {})
    };

    return exampleJson;
  } catch (error) {
    throw error;
  }
};
