import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { ToastrService } from 'ngx-toastr';
import { JoyrideService } from 'ngx-joyride';
import { FlatTreeControl } from '@angular/cdk/tree';
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
import { Subscription } from 'rxjs';
import { DataService } from './services/data.service';
import { UnidadOrganizativa, Client, TreeNode, FlatNode, Command } from './model/model';
import { ManageOrganizationalUnitComponent } from './shared/organizational-units/manage-organizational-unit/manage-organizational-unit.component';
import { ShowOrganizationalUnitComponent } from './shared/organizational-units/show-organizational-unit/show-organizational-unit.component';
import { LegendComponent } from './shared/legend/legend.component';
import { DeleteModalComponent } from '../../shared/delete_modal/delete-modal/delete-modal.component';
import { ClassroomViewDialogComponent } from './shared/classroom-view/classroom-view-modal';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { PageEvent } from '@angular/material/paginator';
import { CreateMultipleClientComponent } from "./shared/clients/create-multiple-client/create-multiple-client.component";
import { SelectionModel } from "@angular/cdk/collections";
import { ManageClientComponent } from "./shared/clients/manage-client/manage-client.component";
import { debounceTime } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { ConfigService } from '@services/config.service';
import { GlobalStatusComponent } from '../global-status/global-status.component';

enum NodeType {
  OrganizationalUnit = 'organizational-unit',
  ClassroomsGroup = 'classrooms-group',
  Classroom = 'classroom',
  ClientsGroup = 'clients-group',
  Client = 'client',
}

@Component({
  selector: 'app-groups',
  templateUrl: './groups.component.html',
  styleUrls: ['./groups.component.css'],
})
export class GroupsComponent implements OnInit, OnDestroy {
  baseUrl: string;
  mercureUrl: string;
  organizationalUnits: UnidadOrganizativa[] = [];
  selectedUnidad: UnidadOrganizativa | null = null;
  selectedDetail: UnidadOrganizativa | null = null;
  length: number = 0;
  itemsPerPage: number = 20;
  page: number = 0;
  pageSizeOptions: number[] = [5, 10, 20, 50, 100];
  initialLoading: boolean = true;
  isLoadingClients: boolean = false;
  searchTerm = '';
  treeControl: FlatTreeControl<FlatNode>;
  treeFlattener: MatTreeFlattener<TreeNode, FlatNode>;
  treeDataSource: MatTreeFlatDataSource<TreeNode, FlatNode>;
  selectedNode: TreeNode | null = null;
  hasClients: boolean = false;
  commands: Command[] = [];
  commandsLoading = false;
  selectedClients = new MatTableDataSource<Client>([]);
  selection = new SelectionModel<any>(true, []);
  cols = 4;
  currentView: string = 'list';
  savedFilterNames: [string, string][] = [];
  selectedTreeFilter = '';
  syncStatus = false;
  syncingClientId: string | null = null;
  private originalTreeData: TreeNode[] = [];
  arrayClients: any[] = [];
  filters: { [key: string]: string } = {};
  private clientFilterSubject = new Subject<string>();

  protected status = [
    { value: 'off', name: 'Apagado' },
    { value: 'initializing', name: 'Inicializando' },
    { value: 'og-live', name: 'Og Live' },
    { value: 'linux', name: 'Linux' },
    { value: 'linux-session', name: 'Linux Session' },
    { value: 'windows', name: 'Windows' },
    { value: 'windows-session', name: 'Windows Session' },
    { value: 'busy', name: 'Ocupado' },
    { value: 'mac', name: 'Mac' },
  ];

  displayedColumns: string[] = ['select', 'status', 'ip', 'name', 'oglive', 'subnet', 'pxeTemplate', 'actions'];

  private _sort!: MatSort;

  @ViewChild(MatSort)
  set matSort(ms: MatSort) {
    this._sort = ms;
    if (this.selectedClients) {
      this.selectedClients.sort = this._sort;
    }
  }

  private subscriptions: Subscription = new Subscription();

  constructor(
    private http: HttpClient,
    private router: Router,
    private dataService: DataService,
    public dialog: MatDialog,
    private bottomSheet: MatBottomSheet,
    private joyrideService: JoyrideService,
    private toastr: ToastrService,
    private configService: ConfigService
  ) {
    this.baseUrl = this.configService.apiUrl;
    this.mercureUrl = this.configService.mercureUrl;
    this.treeFlattener = new MatTreeFlattener<TreeNode, FlatNode>(
      this.transformer,
      (node) => node.level,
      (node) => node.expandable,
      (node) => node.children
    );

    this.treeControl = new FlatTreeControl<FlatNode>(
      (node) => node.level,
      (node) => node.expandable
    );

    this.treeDataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
    this.currentView = localStorage.getItem('groupsView') || 'list';
  }


  ngOnInit(): void {
    this.updateGridCols();
    this.refreshData();
    window.addEventListener('resize', this.updateGridCols);

    this.selectedClients.filterPredicate = (client: Client, filter: string): boolean => {
      const lowerTerm = filter.toLowerCase();
      return (
        client.name.toLowerCase().includes(lowerTerm) ||
        client.ip?.toLowerCase().includes(lowerTerm) ||
        client.status?.toLowerCase().includes(lowerTerm) ||
        client.mac?.toLowerCase().includes(lowerTerm)
      );
    };

    this.arrayClients = this.selectedClients.data;

    const eventSource = new EventSource(`${this.mercureUrl}?topic=`
      + encodeURIComponent(`clients`));

    eventSource.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data && data['@id']) {
        this.updateClientStatus(data['@id'], data.status);
      }
    }

    this.clientFilterSubject.pipe(debounceTime(500)).subscribe(searchTerm => {
      this.filters['query'] = searchTerm;
      this.filterClients(searchTerm);
    });
  }

  private updateClientStatus(clientUuid: string, newStatus: string): void {
    const clientIndex = this.selectedClients.data.findIndex(client => client['@id'] === clientUuid);

    if (clientIndex !== -1) {
      const updatedClients = [...this.selectedClients.data];

      updatedClients[clientIndex] = {
        ...updatedClients[clientIndex],
        status: newStatus
      };

      this.selectedClients.data = updatedClients;
      this.arrayClients = updatedClients;

      console.log(`Estado actualizado para el cliente ${clientUuid}: ${newStatus}`);
    } else {
      console.warn(`Cliente con UUID ${clientUuid} no encontrado en la lista.`);
    }
  }


  ngOnDestroy(): void {
    window.removeEventListener('resize', this.updateGridCols);
    this.subscriptions.unsubscribe();
  }


  private transformer = (node: TreeNode, level: number): FlatNode => ({
    id: node.id,
    name: node.name,
    type: node.type,
    level,
    expandable: !!node.children?.length,
    hasClients: node.hasClients,
    ip: node.ip,
    '@id': node['@id'],
    networkSettings: node.networkSettings,
  });


  toggleView(view: 'card' | 'list'): void {
    this.currentView = view;
  }


  updateGridCols = (): void => {
    const width = window.innerWidth;
    this.cols = width <= 600 ? 1 : width <= 960 ? 2 : width <= 1280 ? 3 : 4;
  };


  clearSelection(): void {
    this.selectedUnidad = null;
    this.selectedDetail = null;
    this.selectedClients.data = [];
    this.selectedNode = null;
  }


  // Función para obtener los filtros guardados actualmente deshabilitada
  // getFilters(): void {
  //   this.subscriptions.add(
  //     this.dataService.getFilters().subscribe(
  //       (data) => {
  //         this.savedFilterNames = data.map((filter: { name: string; uuid: string; }) => [filter.name, filter.uuid]);
  //       },
  //       (error) => {
  //         console.error('Error fetching filters:', error);
  //       }
  //     )
  //   );
  // }


  getFilters(): void {
    this.subscriptions.add(
      this.dataService.getFilters().subscribe(
        (data) => {
          this.savedFilterNames = data.map((filter: { name: string; uuid: string; }) => [filter.name, filter.uuid]);
        },
        (error) => {
          console.error('Error fetching filters:', error);
        }
      )
    );
  }


  private convertToTreeData(data: UnidadOrganizativa): TreeNode {
    const processNode = (node: UnidadOrganizativa): TreeNode => {
      const children = node.children?.map(processNode) || [];
      const hasClients = (node.clients?.length ?? 0) > 0 || children.some(child => child.hasClients);

      return {
        id: node.id,
        uuid: node.uuid,
        name: node.name,
        type: node.type,
        '@id': node['@id'],
        children: children,
        hasClients: hasClients,
      };
    };

    return processNode(data);
  }


  private refreshData(selectedNodeIdOrUuid?: string, selectedClientsBeforeEdit: string[] = []): void {
    this.dataService.getOrganizationalUnits().subscribe({
      next: (data) => {
        this.originalTreeData = data.map((unidad) => this.convertToTreeData(unidad));
        this.treeDataSource.data = [...this.originalTreeData];
        if (selectedNodeIdOrUuid) {
          this.selectedNode = this.findNodeByIdOrUuid(this.treeDataSource.data, selectedNodeIdOrUuid);

          if (this.selectedNode) {
            this.treeControl.collapseAll();
            this.expandPathToNode(this.selectedNode);
            this.fetchClientsForNode(this.selectedNode, selectedClientsBeforeEdit);
          }
        } else {
          this.treeControl.collapseAll();
          if (this.treeDataSource.data.length > 0) {
            this.selectedNode = this.treeDataSource.data[0];
            this.fetchClientsForNode(this.selectedNode, selectedClientsBeforeEdit);
          } else {
            this.selectedNode = null;
            this.selectedClients.data = [];
            this.initialLoading = false;
          }
        }
      },
      error: (error) => {
        console.error('Error fetching organizational units', error);
        this.toastr.error('Ocurrió un error al cargar las unidades organizativas');
      },
    });
  }


  expandPathToNode(node: TreeNode): void {
    const path: TreeNode[] = [];
    let currentNode: TreeNode | null = node;

    while (currentNode) {
      path.unshift(currentNode);
      currentNode = currentNode.id ? this.findParentNode(this.treeDataSource.data, currentNode.id) : null;
    }

    path.forEach((pathNode) => {
      const flatNode = this.treeControl.dataNodes?.find((n) => n.id === pathNode.id);
      if (flatNode) {
        this.treeControl.expand(flatNode);
      }
    });
  }


  private findParentNode(treeData: TreeNode[], childId: string): TreeNode | null {
    for (const node of treeData) {
      if (node.children?.some((child) => child.id === childId)) {
        return node;
      }

      if (node.children && node.children.length > 0) {
        const parent = this.findParentNode(node.children, childId);
        if (parent) {
          return parent;
        }
      }
    }
    return null;
  }


  private findNodeByIdOrUuid(treeData: TreeNode[], identifier: string): TreeNode | null {
    const search = (nodes: TreeNode[]): TreeNode | null => {
      for (const node of nodes) {
        if (node.id === identifier || node.uuid === identifier) return node;
        if (node.children && node.children.length > 0) {
          const found = search(node.children);
          if (found) return found;
        }
      }
      return null;
    };
    return search(treeData);
  }


  onNodeClick(event: MouseEvent, node: TreeNode): void {
    event.stopPropagation();
    this.selectedNode = node;
    this.fetchClientsForNode(node);
  }

  onMenuClick(event: Event, node: any): void {
    event.stopPropagation();
  }


  public fetchClientsForNode(node: any, selectedClientsBeforeEdit: string[] = []): void {
    const params = new HttpParams({ fromObject: this.filters });

    this.isLoadingClients = true;
    this.http.get<any>(`${this.baseUrl}/clients?organizationalUnit.id=${node.id}&page=${this.page + 1}&itemsPerPage=${this.itemsPerPage}`, { params }).subscribe({
      next: (response: any) => {
        this.selectedClients.data = response['hydra:member'];
        this.length = response['hydra:totalItems'];
        this.arrayClients = this.selectedClients.data;
        this.hasClients = node.hasClients ?? false;
        this.isLoadingClients = false;
        this.initialLoading = false;
        this.selection.clear();
        selectedClientsBeforeEdit.forEach(uuid => {
          const client = this.selectedClients.data.find(client => client.uuid === uuid);
          if (client) {
            this.selection.select(client);
          }
        });
      },
      error: () => {
        this.isLoadingClients = false;
        this.initialLoading = false;
      }
    });
  }

  onPageChange(event: PageEvent): void {
    this.page = event.pageIndex;
    this.itemsPerPage = event.pageSize;
    this.fetchClientsForNode(this.selectedNode);
  }

  addOU(event: MouseEvent, parent: TreeNode | null = null): void {
    event.stopPropagation();
    const dialogRef = this.dialog.open(ManageOrganizationalUnitComponent, {
      data: { parent },
      width: '900px',
    });
    dialogRef.afterClosed().subscribe((newUnit) => {
      if (newUnit?.uuid) {
        this.refreshData(newUnit.uuid);
      }
    });
  }


  addClient(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
    event.stopPropagation();
    const targetNode = organizationalUnit || this.selectedNode;
    const dialogRef = this.dialog.open(ManageClientComponent, {
      data: { organizationalUnit: targetNode },
      width: '900px',
    });

    dialogRef.afterClosed().subscribe((result) => {
      if (result?.client && result?.organizationalUnit) {
        const organizationalUnitUrl = result.organizationalUnit;
        const uuid = organizationalUnitUrl.split('/')[2];
        const parentNode = this.findNodeByIdOrUuid(this.treeDataSource.data, uuid);

        if (parentNode) {
          this.refreshData(parentNode.uuid);
        }
      }
    });
  }


  addMultipleClients(event: MouseEvent, organizationalUnit: TreeNode | null = null): void {
    event.stopPropagation();
    const targetNode = organizationalUnit || this.selectedNode;

    const dialogRef = this.dialog.open(CreateMultipleClientComponent, {
      data: { organizationalUnit: targetNode },
      width: '900px',
    });
    dialogRef.afterClosed().subscribe((result) => {
      if (result?.success) {
        const organizationalUnitUrl = result.organizationalUnit;
        const uuid = organizationalUnitUrl.split('/')[2];
        const parentNode = this.findNodeByIdOrUuid(this.treeDataSource.data, uuid);

        if (parentNode) {
          console.log('Nodo padre encontrado para actualización:', parentNode);
          this.refreshData(parentNode.uuid);
        } else {
          console.error('No se encontró el nodo padre después de la creación masiva.');
        }
      }
    });
  }


  onEditNode(event: MouseEvent, node: TreeNode | null): void {
    event.stopPropagation();
    const uuid = node ? this.extractUuid(node['@id']) : null;
    if (!uuid) return;

    const dialogRef = node?.type !== NodeType.Client
      ? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
      : this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px' });

    dialogRef.afterClosed().subscribe(() => {
      if (node) {
        this.refreshData(node.id);
      }
    });
  }

  onDeleteClick(event: MouseEvent, entity: TreeNode | Client | null): void {
    event.stopPropagation();
    if (!entity) return;

    const uuid = entity['@id'] ? this.extractUuid(entity['@id']) : null;
    const type = entity.hasOwnProperty('mac') ? NodeType.Client : NodeType.OrganizationalUnit;

    if (!uuid) return;

    const dialogRef = this.dialog.open(DeleteModalComponent, {
      width: '400px',
      data: { name: entity.name },
    });

    dialogRef.afterClosed().subscribe((result) => {
      if (result === true) {
        this.deleteEntityorClient(uuid, type);
      }
    });
  }


  private deleteEntityorClient(uuid: string, type: string): void {
    if (!this.selectedNode) return;

    const parentNode = this.selectedNode?.id
      ? this.findParentNode(this.treeDataSource.data, this.selectedNode.id)
      : null;

    this.dataService.deleteElement(uuid, type).subscribe({
      next: () => {
        const entityType = type === NodeType.Client ? 'Cliente' : 'Entidad';
        const verb = type === NodeType.Client ? 'eliminado' : 'eliminada';

        this.toastr.success(`${entityType} ${verb} exitosamente`);

        if (type === NodeType.Client) {
          this.refreshData(this.selectedNode?.id);
        } else if (parentNode) {
          this.refreshData(parentNode.id);
        } else {
          this.refreshData();
        }
      },
      error: (error) => {
        console.error('Error deleting entity:', error);
        const entityType = type === NodeType.Client ? 'cliente' : 'entidad';
        this.toastr.error(`Error al eliminar el ${entityType}`);
      },
    });
  }


  onEditClick(event: MouseEvent, type: string, uuid: string): void {
    event.stopPropagation();
    const selectedClientsBeforeEdit = this.selection.selected.map(client => client.uuid);
    const dialogRef = type !== NodeType.Client
      ? this.dialog.open(ManageOrganizationalUnitComponent, { data: { uuid }, width: '900px' })
      : this.dialog.open(ManageClientComponent, { data: { uuid }, width: '900px' });

    dialogRef.afterClosed().subscribe(() => {
      this.refreshData(this.selectedNode?.id, selectedClientsBeforeEdit);
    });
  }


  onRoomMap(room: TreeNode | null): void {
    if (!room || !room['@id']) return;
    this.subscriptions.add(
      this.http.get<{ clients: Client[] }>(`${this.baseUrl}${room['@id']}`).subscribe(
        (response) => {
          this.dialog.open(ClassroomViewDialogComponent, {
            width: '90vw',
            data: { clients: response.clients },
          });
        },
        (error) => {
          console.error('Error fetching room data:', error);
        }
      )
    );
  }


  executeCommand(command: Command, selectedNode: TreeNode | null): void {

    if (!selectedNode) {
      this.toastr.error('No hay un nodo seleccionado.');
      return;
    } else {
      this.toastr.success(`Ejecutando comando: ${command.name} en ${selectedNode.name}`);
    }
  }


  onShowClientDetail(event: MouseEvent, client: Client): void {
    event.stopPropagation();
    this.router.navigate(['clients', client.uuid], { state: { clientData: client } });
  }


  onShowDetailsClick(event: MouseEvent, data: TreeNode | null): void {
    event.stopPropagation();
    if (data && data.type !== NodeType.Client) {
      this.dialog.open(ShowOrganizationalUnitComponent, { data: { data }, width: '800px' });
    } else {
      if (data) {
        this.router.navigate(['clients', this.extractUuid(data['@id'])], { state: { clientData: data } });
      }
    }
  }


  openBottomSheet(): void {
    this.bottomSheet.open(LegendComponent);
  }


  iniciarTour(): void {
    this.joyrideService.startTour({
      steps: ['groupsTitleStepText', 'filtersPanelStep', 'addStep', 'keyStep', 'tabsStep'],
      showPrevButton: true,
      themeColor: '#3f51b5',
    });
  }


  hasChild = (_: number, node: FlatNode): boolean => node.expandable;
  isLeafNode = (_: number, node: FlatNode): boolean => !node.expandable;


  filterTree(searchTerm: string): void {
    const expandPaths: TreeNode[][] = [];

    const filterNodes = (nodes: TreeNode[], parentPath: TreeNode[] = []): TreeNode[] => {
      return nodes
        .map((node) => {
          const matchesName = node.name.toLowerCase().includes(searchTerm.toLowerCase());
          const filteredChildren = node.children ? filterNodes(node.children, [...parentPath, node]) : [];

          if (matchesName) {
            expandPaths.push([...parentPath, node]);
            return {
              ...node,
              children: node.children,
            } as TreeNode;
          } else if (filteredChildren.length > 0) {
            return {
              ...node,
              children: filteredChildren,
            } as TreeNode;
          }
          return null;
        })
        .filter((node): node is TreeNode => node !== null);
    };

    if (!searchTerm) {
      this.treeDataSource.data = [...this.originalTreeData];
      this.treeControl.collapseAll();
    } else {
      this.treeDataSource.data = filterNodes(this.originalTreeData);
      expandPaths.forEach((path) => this.expandPath(path));
    }
  }


  private expandPath(path: TreeNode[]): void {
    path.forEach((pathNode) => {
      const flatNode = this.treeControl.dataNodes?.find((n) => n.id === pathNode.id);
      if (flatNode) {
        this.treeControl.expand(flatNode);
      }
    });
  }


  onTreeFilterInput(event: Event): void {
    const input = event.target as HTMLInputElement;
    const searchTerm = input?.value.trim() || '';
    this.filterTree(searchTerm);
  }


  onClientFilterInput(event: Event): void {
    const input = event.target as HTMLInputElement;
    const searchTerm = input?.value || '';
    this.clientFilterSubject.next(searchTerm);
  }

  onClientFilterStatusInput(event: Event): void {
    // @ts-ignore
    this.filters['status'] = event;
    this.fetchClientsForNode(this.selectedNode);
  }


  filterClients(searchTerm: string): void {
    this.searchTerm = searchTerm.trim().toLowerCase();
    //this.selectedClients.filter = this.searchTerm;

    this.fetchClientsForNode(this.selectedNode);
    this.arrayClients = this.selectedClients.filteredData;
  }


  public setSelectedNode(node: TreeNode): void {
    this.selectedNode = node;
  }


  getStatus(client: Client, node: any): void {
    if (!client.uuid || !client['@id']) return;

    this.syncingClientId = client.uuid;
    this.syncStatus = true;

    const parentNodeId = client.organizationalUnit?.id || node.id;
    console.log('Parent node id:', parentNodeId);

    this.subscriptions.add(
      this.http.post(`${this.baseUrl}${client['@id']}/agent/status`, {}).subscribe(
        () => {
          this.toastr.success('Cliente actualizado correctamente');
          this.syncStatus = false;
          this.syncingClientId = null;
          this.refreshData(parentNodeId)
        },
        () => {
          this.toastr.error('Error de conexión con el cliente');
          this.syncStatus = false;
          this.syncingClientId = null;
          this.refreshData(parentNodeId)
        }
      )
    );
  }


  isAllSelected() {
    const numSelected = this.selection.selected.length;
    const numRows = this.selectedClients.data.length;
    return numSelected === numRows;
  }


  toggleAllRows() {
    if (this.isAllSelected()) {
      this.selection.clear();
    } else {
      this.selection.select(...this.selectedClients.data);
    }
    this.updateSelectedClients();
  }

  toggleAllCards() {
    if (this.isAllSelected()) {
      this.selection.clear();
    } else {
      this.selection.select(...this.selectedClients.data);
    }
    this.updateSelectedClients();
  }


  toggleRow(row: any) {
    this.selection.toggle(row);
    this.updateSelectedClients();
  }


  updateSelectedClients() {
    this.arrayClients = this.selectedClients.data;
  }


  getClientPath(client: Client): string {
    const path: string[] = [];
    let currentNode: TreeNode | null = this.findNodeByIdOrUuid(this.treeDataSource.data, client.organizationalUnit.uuid);

    while (currentNode) {
      path.unshift(currentNode.name);
      currentNode = currentNode.id ? this.findParentNode(this.treeDataSource.data, currentNode.id) : null;
    }

    return path.join(' / ');
  }


  private extractUuid(idPath: string | undefined): string | null {
    return idPath ? idPath.split('/').pop() || null : null;
  }


  clearTreeSearch(inputElement: HTMLInputElement): void {
    inputElement.value = '';
    this.filterTree('');
  }


  clearClientSearch(inputElement: HTMLInputElement): void {
    inputElement.value = '';
    delete this.filters['query'];
    this.filterClients('');
  }

  clearStatusFilter(event: Event, clientSearchStatusInput: any): void {
    event.stopPropagation();
    delete this.filters['status'];
    clientSearchStatusInput.value = null;
    this.fetchClientsForNode(this.selectedNode);
  }
}
