<?php

namespace LakeDrops\DockerTraefik;

use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Yaml\Yaml;

/**
 * Class Traefik.
 *
 * @package LakeDrops\DockerTraefik
 */
class Traefik {

  /**
   * The name of the project using Traefik.
   *
   * @var string
   */
  protected $name;

  /**
   * @var string
   */
  protected $domain;

  /**
   * @var int
   */
  protected $http_port;

  /**
   * @var int
   */
  protected $https_port;

  /**
   * @var string
   */
  protected $cert_filename;

  /**
   * @var string
   */
  protected $key_filename;

  /**
   * @var bool
   */
  protected $addon_portainer = FALSE;

  /**
   * Traefik constructor.
   *
   * @param string $name
   *   Name of the network to be created which needs to match the container
   *   prefix of your project you would like to handle with Traefik.
   * @param string $domain
   *   The domain name used for all local projects.
   * @param int $http_port
   *   Port for non secure requests.
   * @param int $https_port
   *   Port for secure requests.
   * @param string $cert_filename
   *   Filename of the SSL certificate.
   * @param string $key_filename
   *   Filename of the private key for the SSL certificate.
   */
  public function __construct(string $name, $domain = 'docker.localhost', $http_port = 8000, $https_port = 8443, $cert_filename = 'fullchain.pem', $key_filename = 'privkey.pem') {
    $this->name = $name;
    $this->domain = $domain;
    $this->http_port = $http_port;
    $this->https_port = $https_port;
    $this->cert_filename = $cert_filename;
    $this->key_filename = $key_filename;
  }

  /**
   * @param bool $addon_portainer
   */
  public function setAddonPortainer(bool $addon_portainer): void {
    $this->addon_portainer = $addon_portainer;
  }

  /**
   * Update the Traefik container.
   */
  public function update(): void {
    // Update host wide traefik container.
    $traefikPath = $_SERVER['HOME'] . '/.traefik';
    $traefikCertPath = $_SERVER['HOME'] . '/.traefik/certs';
    $traefikConfigPath = $_SERVER['HOME'] . '/.traefik/configuration';
    $traefikFile = $traefikPath . '/docker-compose.yml';

    $fs = new Filesystem();
    if (!$fs->exists($traefikPath)) {
      $fs->mkdir($traefikPath);
    }
    if (!$fs->exists($traefikCertPath)) {
      $fs->mkdir($traefikCertPath);
    }
    if (!$fs->exists($traefikConfigPath)) {
      $fs->mkdir($traefikConfigPath);
    }
    file_put_contents($traefikConfigPath . '/certificates.toml', $this->defaultCertificatesConfig());
    file_put_contents($traefikFile, Yaml::dump($this->defaultDockerCompose(), 9, 2));
    $cwd = getcwd();
    chdir($traefikPath);
    exec('docker network create traefik-public 2>/dev/null');
    exec('docker compose --project-name traefik up -d');
    chdir($cwd);
  }

  /**
   * @return array
   */
  private function defaultDockerCompose(): array {
    $config = [
      'version' => '3.3',
      'services' => [
        'traefik' => [
          'image' => 'traefik:2.6',
          'command' => [
            '--api=true',
            '--api.dashboard=true',
            '--api.insecure=true',
            '--entrypoints.web.address=:80',
            '--entrypoints.websecure.address=:443',
            '--entrypoints.websecure.http.tls.domains[0].main=' . $this->domain,
            '--entrypoints.websecure.http.tls.domains[0].sans=.' . $this->domain,
            '--providers.file.directory=/configuration/',
            '--providers.file.watch=true',
            '--providers.docker=true',
            '--providers.docker.exposedbydefault=false',
          ],
          'restart' => 'unless-stopped',
          'networks' => [
            'traefik-public',
          ],
          'ports' => [
            $this->http_port . ':80',
            $this->https_port . ':443',
          ],
          'labels' => [
            'traefik.enable=true',
            'traefik.network=traefik-public',
            'traefik.http.routers.traefik.service=api@internal',
            'traefik.http.routers.traefik.rule=Host(`traefik.' . $this->domain . '`)',
          ],
          'volumes' => [
            './certs:/certs/:ro',
            './configuration:/configuration/:ro',
            '/var/run/docker.sock:/var/run/docker.sock:ro',
          ],
        ],
      ],
      'networks' => [
        'traefik-public' => [
          'external' => true,
        ],
      ],
    ];
    if ($this->addon_portainer) {
      $config['services']['portainer'] = [
        'image' => 'portainer/portainer-ce:2.11.1',
        'command' => '-H unix:///var/run/docker.sock',
        'restart' => 'unless-stopped',
        'networks' => [
          'traefik-public',
        ],
        'labels' => [
          'traefik.enable=true',
          'traefik.network=traefik-public',
          'traefik.http.routers.frontend.rule=Host(`portainer.' . $this->domain . '`)',
          'traefik.http.routers.frontend.entrypoints=websecure',
          'traefik.http.services.frontend.loadbalancer.server.port=9000',
          'traefik.http.routers.frontend.service=frontend',
          'traefik.http.routers.edge.rule=Host(`edge.' . $this->domain . '`)',
          'traefik.http.routers.edge.entrypoints=websecure',
          'traefik.http.services.edge.loadbalancer.server.port=8000',
          'traefik.http.routers.edge.service=edge',
        ],
        'volumes' => [
          './portainerdata:/data',
          '/var/run/docker.sock:/var/run/docker.sock:ro',
        ],
      ];
    }
    return $config;
  }

  /**
   * @return string
   */
  private function defaultCertificatesConfig(): string {
    return <<<EOF
[[tls.certificates]]
   certFile = "/certs/$this->cert_filename"
   keyFile = "/certs/$this->key_filename"
EOF;

  }

}