import { CliTerseError } from '@alwaysai/alwayscli';
import { PLEASE_REPORT_THIS_ERROR_MESSAGE } from '../constants';
import { serviceEndpointBuilder } from '../infrastructure/urls';
import { logger, stringifyError } from '../util';
import { CliAuthenticationClient } from './authentication-client';
import { getRestURL } from './urls';

export type DeviceMode = 'development' | 'production';

export type Cert = {
  iotKeys: {
    certificateArn: string;
    certificateId: string;
    certificatePem: string;
    keyPair: {
      PublicKey: string;
      PrivateKey: string;
    };
    rootCA: string;
  };
};

export type AddedDevice = {
  deviceId: number;
  deviceUUID: string;
  accessToken: string;
  refreshToken: string;
  idToken: string;
  iotKeys: {
    certificateArn: string;
    certificateId: string;
    certificatePem: string;
    keyPair: {
      PublicKey: string;
      PrivateKey: string;
    };
    rootCA: string;
  };
};

export type AaiDevice = {
  id: number;
  uuid: string;
  owner: string;
  friendly_name: string;
  description?: string;
  host_name: string;
  device_user_name?: string;
  hardware_ids?: string;
  device_hash?: string;
  deleted: boolean;
  created_at: string;
  updated_at: string;
  cognito_device_key?: string;
  mode?: string;
  iot_keys?: any;
  status?: string;
  device_log?: any;
  unit?: string;
  ip?: string;
  coordinates?: any;
};

export type RefreshedDevice = {
  accessToken: string;
  refreshToken: string;
  idToken: string;
};

export type ReleaseURLandKey = {
  key: string;
  presignedAppUrl: string;
};

export type ReleaseHistoryArray = ReleaseHistoryDict[];

export type ReleaseHistoryDict = {
  name: string;
  hash: string;
  version: string;
  timestamp: string;
  releaseHash: string; // this is the same as hash
  created_at: string;
  size: number;
};

export type ApplicationPackage = {
  tarFileName: string;
  tarFile: Buffer;
  releaseManifestName: string;
  releaseManifestFile: string;
};

export type ApplicationRecord = {
  hash: string;
  s3Path: string;
  projectId: number;
  name: string;
};

export type PipelineChannel = {
  name: string;
  uuid: string;
};

export async function addDevice(device, deviceMode: DeviceMode) {
  const { getIdAuthorizationHeader } = CliAuthenticationClient();

  const idTokenAuthorizationHeader = await getIdAuthorizationHeader();
  const requestURL = `${getRestURL()}/createDeviceIot`;

  const response = await fetch(requestURL, {
    method: 'post',
    body: JSON.stringify({ ...device, deviceMode }),
    headers: {
      ...idTokenAuthorizationHeader,
      'Content-Type': 'application/json'
    }
  });

  if (response.status !== 200) {
    logger.error(`addDevice: ${response.statusText}`);
    throw new CliTerseError(
      `addDevice: ${response.statusText}. ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
    );
  }

  const readerStream = Buffer.from(await response.arrayBuffer());
  const parsedJSon = JSON.parse(readerStream.toString());

  return parsedJSon as AddedDevice;
}

export async function refreshDevice(deviceId: string) {
  const { getIdAuthorizationHeader } = CliAuthenticationClient();

  const idTokenAuthorizationHeader = await getIdAuthorizationHeader();
  const requestURL = `${getRestURL()}/refreshDevice`;

  const response = await fetch(requestURL, {
    method: 'post',
    body: JSON.stringify({ deviceId }),
    headers: {
      ...idTokenAuthorizationHeader,
      'Content-Type': 'application/json'
    }
  });

  if (response.status !== 200) {
    logger.error(`refreshDevice: ${response.statusText}`);
    throw new CliTerseError(
      `refreshDevice: ${response.statusText}. ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
    );
  }

  const readerStream = Buffer.from(await response.arrayBuffer());
  const parsedJSon = JSON.parse(readerStream.toString());

  return parsedJSon as RefreshedDevice;
}

interface UploadResult {
  uploadURL: string;
}
export async function getPresignedUrlAppUpload(
  fileName: string,
  fileType: string
): Promise<UploadResult> {
  const { getIdAuthorizationHeader } = CliAuthenticationClient();

  const idTokenAuthorizationHeader = await getIdAuthorizationHeader();

  const requestURL = serviceEndpointBuilder(
    'remote-deployment',
    'presigned-url-app-upload'
  );

  const response = await fetch(requestURL, {
    method: 'post',
    body: JSON.stringify({ fileName, fileType }),
    headers: {
      ...idTokenAuthorizationHeader,
      'Content-Type': 'application/json'
    }
  });

  if (response.status !== 200) {
    logger.error(`get-presigned-url-app-upload: ${response.statusText}`);
    throw new CliTerseError(
      `get-presigned-url-app-upload: ${response.statusText}. ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
    );
  }

  const readerStream = Buffer.from(await response.arrayBuffer());
  const parsedJSon = JSON.parse(readerStream.toString());
  return parsedJSon;
}

export async function usePresignedUrlAppUpload(
  presignedUrl: string,
  fileStream: any
) {
  try {
    const response = await fetch(presignedUrl, {
      method: 'PUT',
      body: fileStream,
      headers: {
        'Content-Type': 'application/octet-stream'
      }
    });

    if (response.status !== 200) {
      logger.error(`use-presigned-url-app-upload: ${response.statusText}`);
      throw new CliTerseError(
        `use-presigned-url-app-upload: ${response.statusText}. ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
      );
    }
  } catch (error) {
    logger.error(stringifyError(error));
    throw new CliTerseError(
      `app-upload: ${stringifyError(
        error
      )}  ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
    );
  }
}

export async function insertAppRecord(record: ApplicationRecord) {
  const { getIdAuthorizationHeader } = CliAuthenticationClient();
  const idTokenAuthorizationHeader = await getIdAuthorizationHeader();

  const response = await fetch(
    serviceEndpointBuilder('remote-deployment', 'create-application-release'),
    {
      method: 'post',
      body: JSON.stringify({ ...record }),
      headers: {
        ...idTokenAuthorizationHeader,
        'Content-Type': 'application/json'
      }
    }
  );
  const readerStream = Buffer.from(await response.arrayBuffer());
  const jsonStr = readerStream.toString();
  if (response.status !== 200) {
    logger.error(`insertAppRecord: ${jsonStr}`);
    throw new CliTerseError(
      `insertAppRecord: ${jsonStr}. ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
    );
  }
}

export async function getReleaseURL(projectId: string, releaseHash?: string) {
  const { getIdAuthorizationHeader } = CliAuthenticationClient();

  const idTokenAuthorizationHeader = await getIdAuthorizationHeader();
  const requestURL = serviceEndpointBuilder(
    'remote-deployment',
    'get-presigned-app-url'
  );

  const response = await fetch(requestURL, {
    method: 'post',
    body: JSON.stringify({ projectId, releaseHash }),
    headers: {
      ...idTokenAuthorizationHeader,
      'Content-Type': 'application/json'
    }
  });

  if (response.status !== 200) {
    logger.error(`getReleaseUrl: ${response.statusText}`);
    throw new CliTerseError(
      `getReleaseUrl: ${response.statusText}. ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
    );
  }

  const readerStream = Buffer.from(await response.arrayBuffer());
  const parsedJSon = JSON.parse(readerStream.toString());

  return parsedJSon as ReleaseURLandKey;
}

export async function fetchAppReleaseHistory(projectUuid: string) {
  const { getIdAuthorizationHeader } = CliAuthenticationClient();

  const idTokenAuthorizationHeader = await getIdAuthorizationHeader();
  const requestURL = serviceEndpointBuilder(
    'remote-deployment',
    'get-app-release-history'
  );

  const response = await fetch(requestURL, {
    method: 'post',
    body: JSON.stringify({ projectUuid }),
    headers: {
      ...idTokenAuthorizationHeader,
      'Content-Type': 'application/json'
    }
  });

  if (response.status !== 200) {
    logger.error(`fetchAppReleaseHistory: ${response.statusText}`);
    throw new CliTerseError(
      `fetchAppReleaseHistory: ${response.statusText}. ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
    );
  }

  const readerStream = Buffer.from(await response.arrayBuffer());
  const parsedJSon = JSON.parse(readerStream.toString());

  return parsedJSon as ReleaseHistoryArray;
}

export async function getDeviceByUuid({
  uuid
}: {
  uuid: string;
}): Promise<AaiDevice> {
  const { getIdAuthorizationHeader } = CliAuthenticationClient();
  const idTokenAuthorizationHeader = await getIdAuthorizationHeader();

  const requestURL = serviceEndpointBuilder('devices', 'get-device-by-uuid');

  const response = await fetch(requestURL, {
    method: 'post',
    body: JSON.stringify({ uuid }),
    headers: {
      ...idTokenAuthorizationHeader,
      'Content-Type': 'application/json'
    }
  });

  if (response.status !== 200) {
    logger.error(`get-device-by-uuid: ${response.statusText}`);
    throw new CliTerseError(
      `get-device-by-uuid: ${response.statusText}. ${PLEASE_REPORT_THIS_ERROR_MESSAGE}`
    );
  }

  const readerStream = Buffer.from(await response.arrayBuffer());
  const parsedJson = JSON.parse(readerStream.toString());
  return parsedJson as AaiDevice;
}

export async function createAnalyticsPipeline(
  pipelineName: string,
  idTokenAuthorizationHeader: Record<string, string>
): Promise<PipelineChannel> {
  const response = await fetch(
    serviceEndpointBuilder('aai-analytics', 'createAnalyticsPipeline'),
    {
      method: 'POST',
      body: JSON.stringify({
        name: pipelineName
      }),
      headers: {
        ...idTokenAuthorizationHeader,
        'Content-Type': 'application/json'
      }
    }
  );

  if (response.status !== 200) {
    logger.error(`createPipeline failed: ${response.statusText}`);
    throw new CliTerseError(`createPipeline failed: ${response.statusText}`);
  }

  const pipeline: PipelineChannel = (await response.json()) as PipelineChannel;
  return pipeline;
}

export async function fetchAnalyticsPipelinesForOrg(
  idTokenAuthorizationHeader: Record<string, string>
): Promise<PipelineChannel[]> {
  const response = await fetch(
    serviceEndpointBuilder(
      'aai-analytics',
      'getAnalyticsChannelIdsForOrganization'
    ),
    {
      method: 'POST',
      body: JSON.stringify({}),
      headers: {
        ...idTokenAuthorizationHeader,
        'Content-Type': 'application/json'
      }
    }
  );

  if (response.status !== 200) {
    logger.error(`fetchPipelinesForOrg failed: ${response.statusText}`);
    throw new CliTerseError(
      `fetchPipelinesForOrg failed: ${response.statusText}`
    );
  }

  const orgPipelines: PipelineChannel[] =
    (await response.json()) as PipelineChannel[];
  return orgPipelines;
}
