import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
import { DetailsComponentBase } from '@objects-view/details-component-base';
import { IInstanceModel } from '@fleio-api/openstack/model/instance.model';
import { map } from 'rxjs/operators';
import { NotificationService } from '@shared/ui-api/notification.service';
import { PortsApiService } from '@fleio-api/openstack/port/ports-api.service';
import { IActionResult } from '@objects-view/interfaces/actions/action-result';
import { MatDialog } from '@angular/material/dialog';
import {
  InstanceDetailsAddPortDialogComponent
} from '@shared/common-dialogs/openstack/instances/instance-details-add-port-dialog/instance-details-add-port-dialog.component';
import { InstanceDetailsAddIpDialogComponent } from '@shared/common-dialogs/openstack/instances/instance-details-add-ip-dialog/instance-details-add-ip-dialog.component';
import {
  InstanceDetailsAutoAddIpDialogComponent
} from '@shared/common-dialogs/openstack/instances/instance-details-auto-add-ip-dialog/instance-details-auto-add-ip-dialog.component';
import { InstancesApiService } from '@fleio-api/openstack/instance/instances-api.service';
import { ConfigService } from '@shared/config/config.service';
import { IPortModel } from '@fleio-api/openstack/model/port.model';
import { InstanceDetailsAttachPortDialogComponent } from '@shared/common-dialogs/openstack/instances/instance-details-attach-port-dialog/instance-details-attach-port-dialog.component';
import { InstanceDetailsAssociateIpDialogComponent } from '@shared/common-dialogs/openstack/instances/instance-details-associate-ip-dialog/instance-details-associate-ip-dialog.component';
import { EditPtrRecordDialogComponent } from '@openstack-instances-dialogs/edit-ptr-record-dialog/edit-ptr-record-dialog.component';
import { FloatingIpSetPtrDialogComponent } from '@shared/common-dialogs/openstack/floating-ips/floating-ip-set-ptr-dialog/floating-ip-set-ptr-dialog.component';

@Component({
  selector: 'app-instance-details-networking',
  templateUrl: './instance-details-networking.component.html',
  styleUrls: ['./instance-details-networking.component.scss']
})
export class InstanceDetailsNetworkingComponent extends DetailsComponentBase<IInstanceModel> implements OnInit {
  permissions: Record<string, unknown>;
  prevNetDetails: { ports: Array<IPortModel> } = null;
  deletePortText = $localize`Delete port`;
  detachPortText = $localize`Detach port`;
  dissociateIpText = $localize`Dissociate ip`;
  removeIpText = $localize`Remove ip`;
  newIPv4 = {};
  newIPv6 = {};

  constructor(
    public config: ConfigService,
    private notificationService: NotificationService,
    private portsApiService: PortsApiService,
    private readonly matDialog: MatDialog,
    private instancesApi: InstancesApiService,
    ngZone: NgZone,
    changeDetectorRef: ChangeDetectorRef,
  ) {
    super(ngZone, changeDetectorRef, config);
  }

  public removePort(port) {
    const dialogResult$ = this.notificationService.confirmDialog({
      title: $localize`Delete port ${port.id}?`,
      message: $localize`Are you sure?`,
    });
    dialogResult$.subscribe(dialogResult => {
      if (dialogResult?.button === 'yes') {
        this.portsApiService.delete(port.id).subscribe(() => {
          this.notificationService.showMessage($localize`Port removal scheduled.`);
          this.boostRefreshTimer();
        }, error => {
          this.notificationService.showMessage($localize`Could not remove port.`);
        });
      }
    });
  }

  public detachPort(port: IPortModel) {
    const dialogResult$ = this.notificationService.confirmDialog({
      title: $localize`Detach port ${port.id}?`,
      message: $localize`Are you sure?`,
      importantMessage: $localize`Note that default ports (ports created automatically on instance create) are automatically deleted by OpenStack when they are detached.`
    });
    dialogResult$.subscribe(dialogResult => {
      if (dialogResult?.button === 'yes') {
        this.instancesApi.detachPort(this.object.id, port.id).pipe(map(success => {
          if (success) {
            this.notificationService.showMessage($localize`Port is being detached.`);
            this.boostRefreshTimer();
          } else {
            this.notificationService.showMessage($localize`Could not detach port.`);
          }
        })).subscribe();
      }
    });
  }

  public removeIp(port, ip) {
    const dialogResult$ = this.notificationService.confirmDialog({
      title: $localize`Delete ip ${ip.ip_address}?`,
      message: $localize`Are you sure?`,
    });
    dialogResult$.subscribe(dialogResult => {
      if (dialogResult?.button === 'yes') {
        this.portsApiService.removeIp(port.id, ip, this.object.region).pipe(map(success => {
          if (success) {
            this.notificationService.showMessage($localize`Remove IP scheduled`);
            this.boostRefreshTimer();
          } else {
            this.notificationService.showMessage($localize`Could not remove ip.`);
          }
        })).subscribe();
      }
    });
  }

  public openAddPortDialog() {
    this.instancesApi.getAddPortOptions(this.object.id).subscribe(response => this.matDialog.open(
        InstanceDetailsAddPortDialogComponent, {
          data: {
            instance: this.object,
            networks: response.networks,
          },
          autoFocus: false,
        }).afterClosed().pipe(map(result => {
        if (result === true || result === 'true') {
          this.boostRefreshTimer();
          this.notificationService.showMessage($localize`Add port scheduled`);
        }
        return {message: result} as IActionResult;
      })).subscribe());
  }

  public openAttachPortDialog() {
    this.portsApiService.list({
      filtering: 'project__project_id:' + this.object.project + '+network__region:' + this.object.region + '+device_id__exact:'
    }).subscribe(response => {
      if (!response.objects?.length) {
        this.notificationService.showMessage($localize`No ports to attach.`);
        return;
      } else {
        return this.matDialog.open(
          InstanceDetailsAttachPortDialogComponent, {
            data: {
              instance: this.object,
            },
            autoFocus: false,
          }).afterClosed().pipe(map(result => {
          if (result === true || result === 'true') {
            this.boostRefreshTimer();
            this.notificationService.showMessage($localize`Port is being attached.`);
          }
          return {message: result} as IActionResult;
        })).subscribe();
      }
    });
  }

  public openAssociateIpDialog() {
    this.instancesApi.getAssociateIpCreateOptions(this.object.id).subscribe(response => this.matDialog.open(
        InstanceDetailsAssociateIpDialogComponent, {
          data: {
            instance: this.object,
            ports: response.ports,
            freeIps: response.free_ips
          },
          autoFocus: false,
        }).afterClosed().pipe(map(result => {
        if (result === true || result === 'true') {
          this.boostRefreshTimer();
          this.notificationService.showMessage($localize`Ip is being associated.`);
        }
        return {message: result} as IActionResult;
      })).subscribe(), error => {
      if (error.error && error.error.detail) {
        const backendMessage = error.error.detail;
        if (error.status === 404) {
          let urlFragment = 'openstack/floating-ips/create';
          let preselectedNetwork = null;
          for (const port of this.object.net_details.ports) {
            if (preselectedNetwork && preselectedNetwork !== port.network) {
              // do not pre-select network if ports are using different networks
              preselectedNetwork = null;
              break;
            }
            preselectedNetwork = port.network;
          }
          if (this.object.client?.id) {
            urlFragment += `/${this.object.client.id}`;
            urlFragment += `/${this.object.region}`;
            urlFragment += `/${this.object.id}`;
            if (preselectedNetwork) {
              urlFragment += `/${preselectedNetwork}`;
            }
          } else {
            urlFragment += `/${this.object.region}`;
            urlFragment += `/${this.object.id}`;
          }
          this.notificationService.showMessageWithLink(
            backendMessage, urlFragment, $localize`Add one`,
          );
        } else {
          this.notificationService.showMessage(backendMessage);
        }
      } else {
        this.notificationService.showMessage($localize`Error when trying to get associate ip options.`);
      }
    });
  }

  public openAddIpDialog() {
    this.instancesApi.getAddIpOptions(this.object.id).subscribe(response => this.matDialog.open(
        InstanceDetailsAddIpDialogComponent, {
          data: {
            instance: this.object,
            ports: response.ports,
            networks: response.networks,
          },
          autoFocus: false,
        }).afterClosed().pipe(map(result => {
        if (result === true || result === 'true') {
          this.boostRefreshTimer();
          this.notificationService.showMessage($localize`Add IP(s) scheduled`);
        }
        return {message: result} as IActionResult;
      })).subscribe());
  }

  public openAddIpAutomaticallyDialog() {
    this.instancesApi.getAddIpOptions(this.object.id).subscribe(response => this.matDialog.open(
        InstanceDetailsAutoAddIpDialogComponent, {
          data: {
            instance: this.object,
            ports: response.ports,
            networks: response.networks,
          },
          autoFocus: false,
        }).afterClosed().pipe(map(result => {
        if (result === true || result === 'true') {
          if (!this.prevNetDetails) {
            this.prevNetDetails = JSON.parse(JSON.stringify(this.object.net_details));
          }
          this.boostRefreshTimer();
          this.notificationService.showMessage($localize`Add IP(s) scheduled`);
        }
        return {message: result} as IActionResult;
      })).subscribe());
  }

  markNewIps(refreshedData) {
    if (this.prevNetDetails) {
      const ipArrays = ['ipv4s', 'ipv6s'];
      for (const ipArray of ipArrays) {
        for (const port of this.prevNetDetails.ports) {
          const refreshedPortIndex = refreshedData.net_details.ports.findIndex(
            (refreshedPort) => port.id === refreshedPort.id
          );
          if (refreshedPortIndex > -1) {
            for (const ip of refreshedData.net_details.ports[refreshedPortIndex][ipArray]) {
              if (port[ipArray].findIndex((element) => element.ip_address === ip.ip_address) === -1) {
                if (ipArray === 'ipv4s') {
                  this.newIPv4[ip.ip_address] = true;
                } else {
                  this.newIPv6[ip.ip_address] = true;
                }
              }
            }
          }
        }
      }
    }
  };

  public dissociateIp(floatingIpAddress) {
    const dialogResult$ = this.notificationService.confirmDialog({
      title: $localize`Disassociate floating IP ${floatingIpAddress}?`,
      message: $localize`Are you sure?`,
    });
    dialogResult$.subscribe(dialogResult => {
      if (dialogResult?.button === 'yes') {
        this.instancesApi.removeFloatingIp(this.object.id, floatingIpAddress).pipe(map(success => {
          if (success) {
            this.notificationService.showMessage($localize`Ip is being removed.`);
            this.boostRefreshTimer();
          } else {
            this.notificationService.showMessage($localize`Could not remove ip.`);
          }
        })).subscribe();
      }
    });
  }

  getPTR(ipAddress) {
    return this.matDialog.open(
      EditPtrRecordDialogComponent, {
        data: {
          region: this.object.region,
          ip: ipAddress
        },
        autoFocus: false,
    }).afterClosed().pipe(map(result => {
      if (result === true || result === 'true') {
        this.boostRefreshTimer();
        this.notificationService.showMessage($localize`PTR updated.`);
      }
      return {message: result} as IActionResult;
    })).subscribe();
  }

  getFloatingIpPTR(ip) {
    return this.matDialog.open(
      FloatingIpSetPtrDialogComponent, {
      data: {floatingIp: ip},
      autoFocus: false,
    }).afterClosed().pipe(map(result => {
      if (result === true || result === 'true') {
        this.boostRefreshTimer();
        this.notificationService.showMessage($localize`PTR updated.`);
      }
      return {message: result} as IActionResult;
    })).subscribe();
  }

  protected refreshData() {
    this.instancesApi.get(this.object.id).subscribe(result => {
      this.markNewIps(result);
    });
  }

  ngOnInit() {
    super.ngOnInit(this.config.getDefaultRefreshInterval(true));
    if (this.objectController) {
      this.permissions = this.objectController.additionalObjects.permissions;
    }
  }

}
