const Docker = require("dockerode");
const { Room } = require("../models/room.js");
const BaseRoomProvider = require("./base-provider.js");

class DockerRoomProvider extends BaseRoomProvider {
  constructor(config, roomRepository) {
    super(config, roomRepository);
    const dockerSocket = process.env.DOCKER_SOCKET || "/var/run/docker.sock";

    this.docker = new Docker({ socketPath: dockerSocket });
    this.docker_network = process.env.QUIZ_NETWORK_NAME || 'evaluetonsavoir_quiz_network';
  }

  async syncInstantiatedRooms() {
    let containers = await this.docker.listContainers();
    containers = containers.filter(container => container.Image === this.quiz_docker_image);

    const containerIds = new Set(containers.map(container => container.Id));

    for (let container of containers) {
      const container_name = container.Names[0].slice(1);
      if (!container_name.startsWith("room_")) {
        console.warn(`Le conteneur ${container_name} ne suit pas la convention de nommage, il sera supprimé.`);
        const curContainer = this.docker.getContainer(container.Id);
        await curContainer.stop();
        await curContainer.remove();
        containerIds.delete(container.Id);
        console.warn(`Le conteneur ${container_name} a été supprimé.`);
      }
      else {
        console.warn(`Conteneur orphelin trouvé : ${container_name}`);
        const roomId = container_name.slice(5);
        const room = await this.roomRepository.get(roomId);

        if (!room) {
          console.warn(`Le conteneur n'est pas dans notre base de données.`);
          const containerInfo = await this.docker.getContainer(container.Id).inspect();
          const containerIP = containerInfo.NetworkSettings.Networks.evaluetonsavoir_quiz_network.IPAddress;
          const host = `${containerIP}:4500`;
          console.warn(`Création de la salle ${roomId} dans notre base de donnée - hôte : ${host}`);
          return await this.roomRepository.create(new Room(roomId, container_name, host));
        }

        console.warn(`La salle ${roomId} est déjà dans notre base de données.`);
      }
    }
  }

  async checkAndPullImage(imageName) {
    try {
      const images = await this.docker.listImages({ all: true });
      //console.log('Images disponibles:', images.map(img => ({
      //  RepoTags: img.RepoTags || [],
      //  Id: img.Id
      //})));
  
      const imageExists = images.some(img => {
        const tags = img.RepoTags || [];
        return tags.includes(imageName) || 
               tags.includes(`${imageName}:latest`) ||
               img.Id.includes(imageName);
      });
  
      if (!imageExists) {
        console.log(`L'image ${imageName} n'a pas été trouvée localement, tentative de téléchargement...`);
        try {
          await this.docker.pull(imageName);
          console.log(`L'image ${imageName} a été téléchargée avec succès`);
        } catch (pullError) {
          const localImages = await this.docker.listImages({ all: true });
          const foundLocally = localImages.some(img => 
            (img.RepoTags || []).includes(imageName) || 
            (img.RepoTags || []).includes(`${imageName}:latest`)
          );
  
          if (!foundLocally) {
            throw new Error(`Impossible de trouver ou de télécharger l'image ${imageName}: ${pullError.message}`);
          } else {
            console.log(`L'image ${imageName} a été trouvée localement après vérification supplémentaire`);
          }
        }
      } else {
        console.log(`L'image ${imageName} a été trouvée localement`);
      }
    } catch (error) {
      throw new Error(`Une erreur est survenue lors de la vérification/téléchargement de l'image ${imageName}: ${error.message}`);
    }
  }

  async createRoom(roomId, options) {
    const container_name = `room_${roomId}`;

    try {
      await this.checkAndPullImage(this.quiz_docker_image);
      const containerConfig = {
        Image: this.quiz_docker_image,
        name: container_name,
        HostConfig: {
          NetworkMode: this.docker_network,
          RestartPolicy: {
            Name: 'unless-stopped'
          }
        },
        Env: [
          `ROOM_ID=${roomId}`,
          `PORT=${this.quiz_docker_port}`,
          ...(options.env || [])
        ]
      };

      if (this.quiz_expose_port) {
        containerConfig.ExposedPorts = {
          [`${this.quiz_docker_port}/tcp`]: {}
        };
        containerConfig.HostConfig.PortBindings = {
          [`${this.quiz_docker_port}/tcp`]: [{ HostPort: '' }] // Empty string for random port
        };
      }

      const container = await this.docker.createContainer(containerConfig);
      await container.start();

      const containerInfo = await container.inspect();
      const networkInfo = containerInfo.NetworkSettings.Networks[this.docker_network];

      if (!networkInfo) {
        throw new Error(`Le conteneur n'as pu se connecter au réseau:  ${this.docker_network}`);
      }

      const containerIP = networkInfo.IPAddress;
      const host = `http://${containerIP}:${this.quiz_docker_port}`;


      let health = false;
      let attempts = 0;
      const maxAttempts = 15;

      while (!health && attempts < maxAttempts) {
        try {
          const response = await fetch(`${host}/health`, {
            timeout: 1000
          });

          if (response.ok) {
            health = true;
            console.log(`Le conteneur  ${container_name} est tombé actif en ${attempts + 1} tentatives`);
          } else {
            throw new Error(`Health check failed with status ${response.status}`);
          }
        } catch (error) {
          attempts++;
          console.log(`Attente du conteneur: ${container_name} (tentative ${attempts}/${maxAttempts})`);
          await new Promise(resolve => setTimeout(resolve, 1000));
        }
      }

      if (!health) {
        console.error(`Container ${container_name} failed health check after ${maxAttempts} attempts`);
        await container.stop();
        await container.remove();
        throw new Error(`Room ${roomId} did not respond within acceptable timeout`);
      }

      return await this.roomRepository.create(new Room(roomId, container_name, host, 0));
    } catch (error) {
      console.error(`Échec de la création de la salle ${roomId}:`, error);
      throw error;
    }
  }

  async deleteRoom(roomId) {
    const container_name = `room_${roomId}`;
    await this.roomRepository.delete(roomId);

    try {
      const container = this.docker.getContainer(container_name);
      const containerInfo = await container.inspect();

      if (containerInfo) {
        await container.stop();
        await container.remove();
        console.log(`Le conteneur pour la salle ${roomId} a été arrêté et supprimé.`);
      }
    } catch (error) {
      if (error.statusCode === 404) {
        console.warn(`Le conteneur pour la salle ${roomId} n'as pas été trouvé, la salle sera supprimée de la base de données.`);
      } else {
        console.error(`Erreur pour la salle ${roomId}:`, error);
        throw new Error("La salle :${roomId} n'as pas pu être supprimée.");
      }
    }

    console.log(`La salle ${roomId} a été supprimée.`);
  }

  async getRoomStatus(roomId) {
    const room = await this.roomRepository.get(roomId);
    if (!room) return null;

    try {
      const container = this.docker.getContainer(room.containerId || `room_${roomId}`);
      const info = await container.inspect();

      const updatedRoomInfo = {
        ...room,
        status: info.State.Running ? "running" : "terminated",
        containerStatus: {
          Running: info.State.Running,
          StartedAt: info.State.StartedAt,
          FinishedAt: info.State.FinishedAt,
        },
        lastUpdate: Date.now(),
      };

      await this.roomRepository.update(updatedRoomInfo);
      return updatedRoomInfo;
    } catch (error) {
      if (error.statusCode === 404) {
        console.warn(`Le conteneur pour la salle ${roomId} n'as pas été trouvé, il sera mis en état "terminé".`);
        const terminatedRoomInfo = {
          ...room,
          status: "terminated",
          containerStatus: {
            Running: false,
            StartedAt: room.containerStatus?.StartedAt || null,
            FinishedAt: Date.now(),
          },
          lastUpdate: Date.now(),
        };

        await this.roomRepository.update(terminatedRoomInfo);
        return terminatedRoomInfo;
      } else {
        console.error(`Une érreur s'est produite lors de l'obtention de l'état de la salle ${roomId}:`, error);
        return null;
      }
    }
  }

  async listRooms() {
    const rooms = await this.roomRepository.getAll();
    return rooms;
  }

  async cleanup() {
    const rooms = await this.roomRepository.getAll();
    for (let room of rooms) {
      if (room.mustBeCleaned) {
        try {
          await this.deleteRoom(room.id);
        } catch (error) {
          console.error(`Érreur lors du néttoyage de la salle ${room.id}:`, error);
        }
      }
    }

    let containers = await this.docker.listContainers();
    containers = containers.filter(container => container.Image === this.quiz_docker_image);
    const roomIds = rooms.map(room => room.id);

    for (let container of containers) {
      if (!roomIds.includes(container.Names[0].slice(6))) {
        const curContainer = this.docker.getContainer(container.Id);
        await curContainer.stop();
        await curContainer.remove();
        console.warn(`Conteneur orphelin ${container.Names[0]} supprimé.`);
      }
    }
  }
}

module.exports = DockerRoomProvider;
