import * as plugins from './plugins.js';
import * as interfaces from './interfaces/index.js';

import { DockerHost } from './classes.host.js';
import { DockerResource } from './classes.base.js';
import { DockerService } from './classes.service.js';
import { logger } from './logger.js';

export class DockerNetwork extends DockerResource {
  // STATIC (Internal - prefixed with _ to indicate internal use)

  /**
   * Internal: Get all networks
   * Public API: Use dockerHost.getNetworks() instead
   */
  public static async _list(
    dockerHost: DockerHost,
  ): Promise<DockerNetwork[]> {
    const dockerNetworks: DockerNetwork[] = [];
    const response = await dockerHost.request('GET', '/networks');
    for (const networkObject of response.body) {
      const dockerNetwork = new DockerNetwork(dockerHost);
      Object.assign(dockerNetwork, networkObject);
      dockerNetworks.push(dockerNetwork);
    }
    return dockerNetworks;
  }

  /**
   * Internal: Get network by name
   * Public API: Use dockerHost.getNetworkByName(name) instead
   */
  public static async _fromName(
    dockerHost: DockerHost,
    dockerNetworkNameArg: string,
  ) {
    const networks = await DockerNetwork._list(dockerHost);
    return networks.find(
      (dockerNetwork) => dockerNetwork.Name === dockerNetworkNameArg,
    );
  }

  /**
   * Internal: Create a network
   * Public API: Use dockerHost.createNetwork(descriptor) instead
   */
  public static async _create(
    dockerHost: DockerHost,
    networkCreationDescriptor: interfaces.INetworkCreationDescriptor,
  ): Promise<DockerNetwork> {
    const response = await dockerHost.request('POST', '/networks/create', {
      Name: networkCreationDescriptor.Name,
      CheckDuplicate: true,
      Driver: networkCreationDescriptor.Driver || 'overlay',
      EnableIPv6: networkCreationDescriptor.EnableIPv6 || false,
      IPAM: networkCreationDescriptor.IPAM,
      Internal: networkCreationDescriptor.Internal || false,
      Attachable: networkCreationDescriptor.Attachable !== undefined ? networkCreationDescriptor.Attachable : true,
      Labels: networkCreationDescriptor.Labels,
      Ingress: false,
    });
    if (response.statusCode < 300) {
      logger.log('info', 'Created network successfully');
      const network = await DockerNetwork._fromName(
        dockerHost,
        networkCreationDescriptor.Name,
      );
      if (!network) {
        throw new Error('Network was created but could not be retrieved');
      }
      return network;
    } else {
      throw new Error('There has been an error creating the wanted network');
    }
  }

  // INSTANCE PROPERTIES
  public Name!: string;
  public Id!: string;
  public Created!: string;
  public Scope!: string;
  public Driver!: string;
  public EnableIPv6!: boolean;
  public Internal!: boolean;
  public Attachable!: boolean;
  public Ingress!: false;
  public IPAM!: {
    Driver: 'default' | 'bridge' | 'overlay';
    Config: [
      {
        Subnet: string;
        IPRange: string;
        Gateway: string;
      },
    ];
  };

  constructor(dockerHostArg: DockerHost) {
    super(dockerHostArg);
  }

  // INSTANCE METHODS

  /**
   * Refreshes this network's state from the Docker daemon
   */
  public async refresh(): Promise<void> {
    const updated = await DockerNetwork._fromName(this.dockerHost, this.Name);
    if (updated) {
      Object.assign(this, updated);
    }
  }

  /**
   * Removes the network
   */
  public async remove() {
    const response = await this.dockerHost.request(
      'DELETE',
      `/networks/${this.Id}`,
    );
  }

  public async listContainersOnNetwork(): Promise<
    Array<{
      Name: string;
      EndpointID: string;
      MacAddress: string;
      IPv4Address: string;
      IPv6Address: string;
    }>
  > {
    const returnArray: any[] = [];
    const response = await this.dockerHost.request(
      'GET',
      `/networks/${this.Id}`,
    );
    for (const key of Object.keys(response.body.Containers)) {
      returnArray.push(response.body.Containers[key]);
    }

    return returnArray;
  }

  public async getContainersOnNetworkForService(serviceArg: DockerService) {
    const containersOnNetwork = await this.listContainersOnNetwork();
    const containersOfService = containersOnNetwork.filter((container) => {
      return container.Name === serviceArg.Spec.Name || container.Name.startsWith(`${serviceArg.Spec.Name}.`);
    });
    return containersOfService;
  }
}
