const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const { exec, execSync } = require('child_process');
const config = require('./config');
const { createRepo, giteaClient } = require('./gitea');

// ============ TEMPLATES DOCKERFILE ============

const DOCKERFILE_TEMPLATES = {
  nodejs: (port) => `# ── Stage 1: Build ──────────────────────────────────────────────
FROM node:22-slim AS builder

WORKDIR /app

# Copy lockfiles for better layer caching
COPY package.json pnpm-lock.yaml* yarn.lock* ./

# Copy patches directory if it exists (needed by pnpm before install)
COPY patches/ ./patches/

# Detect package manager and install ALL dependencies (including devDependencies for build)
RUN if [ -f pnpm-lock.yaml ]; then \\
      corepack enable && corepack prepare pnpm@latest --activate && \\
      pnpm install --no-frozen-lockfile; \\
    elif [ -f yarn.lock ]; then \\
      yarn install; \\
    else \\
      npm install --legacy-peer-deps; \\
    fi

# Copy source code
COPY . .

# Build the application (frontend + backend if applicable)
RUN if [ -f package.json ] && grep -q '"build"' package.json; then \\
      if [ -f pnpm-lock.yaml ]; then \\
        pnpm run build; \\
      elif [ -f yarn.lock ]; then \\
        yarn build; \\
      else \\
        npm run build; \\
      fi; \\
    fi

# ── Stage 2: Production ─────────────────────────────────────────
FROM node:22-slim

WORKDIR /app

# Copy package files
COPY package.json pnpm-lock.yaml* yarn.lock* ./

# Copy patches directory if it exists (needed by pnpm for install)
COPY patches/ ./patches/

# Install ALL dependencies (some projects import dev deps at runtime like vite)
RUN if [ -f pnpm-lock.yaml ]; then \\
      corepack enable && corepack prepare pnpm@latest --activate && \\
      pnpm install --no-frozen-lockfile; \\
    elif [ -f yarn.lock ]; then \\
      yarn install; \\
    else \\
      npm install --legacy-peer-deps; \\
    fi

# Copy built artifacts from builder
COPY --from=builder /app/dist ./dist

# Copy drizzle migrations if they exist
COPY drizzle/ ./drizzle/
COPY drizzle.config.ts* ./

ENV NODE_ENV=production
ENV PORT=\${port}
EXPOSE \${port}

# Start the application
CMD if grep -q '"start"' package.json; then \\
      npm start; \\
    elif [ -f dist/index.js ]; then \\
      node dist/index.js; \\
    else \\
      node index.js; \\
    fi
`,

  'nodejs-next': (port) => `# ── Stage 1: Build ──────────────────────────────────────────────
FROM node:22-slim AS builder

WORKDIR /app
COPY package.json pnpm-lock.yaml* yarn.lock* ./
RUN if [ -f pnpm-lock.yaml ]; then \\
      corepack enable && corepack prepare pnpm@latest --activate && \\
      pnpm install --no-frozen-lockfile; \\
    elif [ -f yarn.lock ]; then \\
      yarn install; \\
    else \\
      npm install --legacy-peer-deps; \\
    fi
COPY . .
RUN if [ -f pnpm-lock.yaml ]; then pnpm run build; \\
    elif [ -f yarn.lock ]; then yarn build; \\
    else npm run build; fi

# ── Stage 2: Production ─────────────────────────────────────────
FROM node:22-slim
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
COPY --from=builder /app/public ./public

ENV NODE_ENV=production
EXPOSE \${port}

CMD ["npm", "start"]
`,

  python: (port) => `FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE \${port}

CMD ["python", "app.py"]
`,

  'python-django': (port) => `FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN python manage.py collectstatic --noinput

EXPOSE \${port}

CMD ["gunicorn", "--bind", "0.0.0.0:\${port}", "config.wsgi:application"]
`,

  'python-flask': (port) => `FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install gunicorn

COPY . .

EXPOSE \${port}

CMD ["gunicorn", "--bind", "0.0.0.0:\${port}", "app:app"]
`,

  php: (port) => `FROM php:8.3-apache

RUN a2enmod rewrite
RUN docker-php-ext-install pdo pdo_mysql mysqli

COPY . /var/www/html/

RUN chown -R www-data:www-data /var/www/html

EXPOSE 80

CMD ["apache2-foreground"]
`,

  'php-laravel': (port) => `FROM php:8.3-fpm

RUN apt-get update && apt-get install -y \\
    git curl zip unzip libpng-dev libonig-dev libxml2-dev \\
    && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www

COPY . .

RUN composer install --no-dev --optimize-autoloader
RUN php artisan config:cache

EXPOSE \${port}

CMD ["php-fpm"]
`,

  static: () => `FROM nginx:alpine

COPY . /usr/share/nginx/html/

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
`,

  'static-react': () => `FROM node:22-slim AS builder

WORKDIR /app
COPY package.json pnpm-lock.yaml* yarn.lock* ./
RUN if [ -f pnpm-lock.yaml ]; then \\
      corepack enable && corepack prepare pnpm@latest --activate && \\
      pnpm install --no-frozen-lockfile; \\
    elif [ -f yarn.lock ]; then \\
      yarn install; \\
    else \\
      npm install --legacy-peer-deps; \\
    fi
COPY . .
RUN if [ -f pnpm-lock.yaml ]; then pnpm run build; \\
    elif [ -f yarn.lock ]; then yarn build; \\
    else npm run build; fi

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
`,
};

// ============ TEMPLATES DOCKER-COMPOSE ============

function generateDockerCompose(appConfig) {
  const { appId, subdomain, stack, port, needsDb, dbType } = appConfig;
  const domain = `${subdomain}.recette.santinova-soft.org`;
  const containerPort = getContainerPort(stack, port);

  let compose = `services:
  app:
    build:
      context: ./src
      dockerfile: Dockerfile
    container_name: ${appId}
    restart: unless-stopped
    networks:
      - web`;

  // Ajouter le réseau interne si base de données
  if (needsDb) {
    compose += `
      - ${appId}-internal`;
  }

  // Variables d'environnement
  compose += `
    environment:
      - NODE_ENV=production`;

  if (needsDb) {
    compose += `
      - DATABASE_HOST=${appId}-db
      - DATABASE_PORT=${dbType === 'postgres' ? '5432' : '3306'}
      - DATABASE_NAME=${appId.replace(/-/g, '_')}
      - DATABASE_USER=app_user
      - DATABASE_PASSWORD=AppPassword2026!
      - DB_HOST=${appId}-db
      - DB_PORT=${dbType === 'postgres' ? '5432' : '3306'}
      - DB_NAME=${appId.replace(/-/g, '_')}
      - DB_USER=app_user
      - DB_PASSWORD=AppPassword2026!
      - MYSQL_HOST=${appId}-db
      - MYSQL_DATABASE=${appId.replace(/-/g, '_')}
      - MYSQL_USER=app_user
      - MYSQL_PASSWORD=AppPassword2026!`;
  }

  compose += `
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.${appId}.rule=Host(\`${domain}\`)"
      - "traefik.http.routers.${appId}.entrypoints=websecure"
      - "traefik.http.routers.${appId}.tls=true"
      - "traefik.http.routers.${appId}.tls.certresolver=letsencrypt"
      - "traefik.http.routers.${appId}.service=${appId}-svc"
      - "traefik.http.services.${appId}-svc.loadbalancer.server.port=${containerPort}"
      - "traefik.docker.network=web"`;

  // Ajouter la base de données si nécessaire
  if (needsDb) {
    const dbName = appId.replace(/-/g, '_');

    if (dbType === 'postgres') {
      compose += `

  db:
    image: postgres:16-alpine
    container_name: ${appId}-db
    restart: unless-stopped
    environment:
      - POSTGRES_DB=${dbName}
      - POSTGRES_USER=app_user
      - POSTGRES_PASSWORD=AppPassword2026!
    volumes:
      - ${appId}-db-data:/var/lib/postgresql/data
    networks:
      - ${appId}-internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_user -d ${dbName}"]
      interval: 10s
      timeout: 5s
      retries: 5`;
    } else {
      compose += `

  db:
    image: mysql:8.0
    container_name: ${appId}-db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=RootPassword2026!
      - MYSQL_DATABASE=${dbName}
      - MYSQL_USER=app_user
      - MYSQL_PASSWORD=AppPassword2026!
    volumes:
      - ${appId}-db-data:/var/lib/mysql
    networks:
      - ${appId}-internal
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-pRootPassword2026!"]
      interval: 10s
      timeout: 5s
      retries: 5`;
    }
  }

  // Dépendance app -> db
  if (needsDb) {
    // Insert depends_on before labels
    const appSection = compose.indexOf('    labels:');
    const dependsOn = `    depends_on:
      db:
        condition: service_healthy
`;
    compose = compose.slice(0, appSection) + dependsOn + compose.slice(appSection);
  }

  // Réseaux
  compose += `

networks:
  web:
    external: true`;

  if (needsDb) {
    compose += `
  ${appId}-internal:
    driver: bridge`;
  }

  // Volumes
  if (needsDb) {
    compose += `

volumes:
  ${appId}-db-data:`;
  }

  compose += '\n';

  return compose;
}

function getContainerPort(stack, customPort) {
  if (customPort) return customPort;
  const defaults = {
    nodejs: 3000,
    'nodejs-next': 3000,
    python: 5000,
    'python-django': 8000,
    'python-flask': 5000,
    php: 80,
    'php-laravel': 9000,
    static: 80,
    'static-react': 80,
  };
  return defaults[stack] || 3000;
}

// ============ APPS PERSISTENCE ============

const APPS_FILE = path.join(config.appsBasePath, '.dashboard-apps.json');

function loadDynamicApps() {
  try {
    if (fs.existsSync(APPS_FILE)) {
      const data = fs.readFileSync(APPS_FILE, 'utf-8');
      return JSON.parse(data);
    }
  } catch (err) {
    console.error('Erreur chargement apps dynamiques:', err.message);
  }
  return [];
}

function saveDynamicApps(apps) {
  try {
    fs.writeFileSync(APPS_FILE, JSON.stringify(apps, null, 2), 'utf-8');
  } catch (err) {
    console.error('Erreur sauvegarde apps dynamiques:', err.message);
  }
}

function addDynamicApp(appDef) {
  const apps = loadDynamicApps();
  // Éviter les doublons
  const existing = apps.findIndex((a) => a.id === appDef.id);
  if (existing >= 0) {
    apps[existing] = appDef;
  } else {
    apps.push(appDef);
  }
  saveDynamicApps(apps);

  // Ajouter aussi à config.apps en mémoire
  const existingInConfig = config.apps.findIndex((a) => a.id === appDef.id);
  if (existingInConfig >= 0) {
    config.apps[existingInConfig] = appDef;
  } else {
    config.apps.push(appDef);
  }
}

function initDynamicApps() {
  const dynamicApps = loadDynamicApps();
  for (const app of dynamicApps) {
    const existingInConfig = config.apps.findIndex((a) => a.id === app.id);
    if (existingInConfig < 0) {
      config.apps.push(app);
    }
  }
  console.log(`Apps dynamiques chargées: ${dynamicApps.length}`);
}

// ============ CREATION D'APPLICATION ============

async function createApplication(params, logCallback) {
  const {
    name,
    description,
    subdomain,
    stack,
    port,
    needsDb,
    dbType,
    sourceType, // 'zip' or 'gitea'
    giteaRepoUrl, // URL du repo Gitea existant
    zipFilePath, // Chemin du fichier ZIP uploadé
  } = params;

  const appId = subdomain;
  const domain = `${subdomain}.recette.santinova-soft.org`;
  const appDir = path.join(config.appsBasePath, appId);
  const srcDir = path.join(appDir, 'src');

  const log = (msg) => {
    const line = `[${new Date().toISOString()}] ${msg}`;
    console.log(line);
    if (logCallback) logCallback(line);
  };

  try {
    // 1. Créer le répertoire de l'application
    log(`Création du répertoire ${appDir}...`);
    await fsp.mkdir(appDir, { recursive: true });
    await fsp.mkdir(srcDir, { recursive: true });

    let giteaRepoName = appId;
    let giteaOwner = config.gitea.username;

    if (sourceType === 'gitea') {
      // 2a. Cloner depuis un dépôt Gitea existant
      log(`Clonage du dépôt Gitea: ${giteaRepoUrl}...`);
      const cloneUrl = giteaRepoUrl.replace(
        'https://',
        `https://${config.gitea.username}:${encodeURIComponent(config.gitea.password)}@`
      );
      await execCommand(`cd ${appDir} && rm -rf src && git clone ${cloneUrl} src`, 120000);
      log('Clonage terminé.');

      // Extraire le nom du repo depuis l'URL
      const urlParts = giteaRepoUrl.replace(/\.git$/, '').split('/');
      giteaRepoName = urlParts[urlParts.length - 1];
      if (urlParts.length >= 2) {
        giteaOwner = urlParts[urlParts.length - 2];
      }
    } else if (sourceType === 'zip') {
      // 2b. Extraire le ZIP et créer un dépôt Gitea
      log(`Extraction du fichier ZIP...`);
      await execCommand(`cd ${srcDir} && unzip -o ${zipFilePath}`, 60000);

      // Vérifier si les fichiers sont dans un sous-répertoire
      const entries = await fsp.readdir(srcDir);
      const nonHidden = entries.filter((e) => !e.startsWith('.') && e !== '__MACOSX');
      if (nonHidden.length === 1) {
        const subDir = path.join(srcDir, nonHidden[0]);
        const stat = await fsp.stat(subDir);
        if (stat.isDirectory()) {
          log(`Déplacement des fichiers depuis le sous-répertoire ${nonHidden[0]}...`);
          await execCommand(`cd ${subDir} && mv * ${srcDir}/ 2>/dev/null; mv .* ${srcDir}/ 2>/dev/null; cd ${srcDir} && rmdir "${nonHidden[0]}" 2>/dev/null || true`);
        }
      }

      // Supprimer le ZIP temporaire
      try { await fsp.unlink(zipFilePath); } catch (e) { /* ignore */ }

      // Créer le dépôt sur Gitea
      log(`Création du dépôt Gitea: ${appId}...`);
      try {
        await createRepo(appId, description || `Application ${name}`, true);
        log('Dépôt Gitea créé.');
      } catch (err) {
        if (err.response && err.response.status === 409) {
          log('Le dépôt Gitea existe déjà, utilisation du dépôt existant.');
        } else {
          throw err;
        }
      }

      // Initialiser git et pousser le code
      log('Initialisation Git et push vers Gitea...');
      const gitUrl = `https://${config.gitea.username}:${encodeURIComponent(config.gitea.password)}@${config.gitea.url.replace('https://', '')}/${config.gitea.username}/${appId}.git`;
      await execCommand(
        `cd ${srcDir} && git init && git checkout -b main && git config user.email "admin@santinova-soft.org" && git config user.name "Manus Dashboard" && git add -A && git commit -m "Initial commit: ${name}" && git remote add origin ${gitUrl} && git push -u origin main --force`,
        120000
      );
      log('Code poussé sur Gitea.');
    }

    // 3. Générer le Dockerfile si nécessaire
    const dockerfilePath = path.join(srcDir, 'Dockerfile');
    const hasDockerfile = fs.existsSync(dockerfilePath);

    if (!hasDockerfile) {
      log(`Génération du Dockerfile pour la stack ${stack}...`);
      const containerPort = getContainerPort(stack, port);
      const templateFn = DOCKERFILE_TEMPLATES[stack] || DOCKERFILE_TEMPLATES['nodejs'];
      let dockerfileContent = templateFn(containerPort);

      // If the project does NOT have a patches/ directory, remove COPY patches/ lines
      const hasPatchesDir = fs.existsSync(path.join(srcDir, 'patches'));
      if (!hasPatchesDir) {
        dockerfileContent = dockerfileContent
          .split('\n')
          .filter(line => !line.includes('COPY patches/'))
          .join('\n');
        log('Pas de dossier patches/ détecté, lignes COPY patches/ retirées du Dockerfile.');
      }

      // If the project does NOT have a drizzle/ directory, remove COPY drizzle lines
      const hasDrizzleDir = fs.existsSync(path.join(srcDir, 'drizzle'));
      if (!hasDrizzleDir) {
        dockerfileContent = dockerfileContent
          .split('\n')
          .filter(line => !line.includes('COPY drizzle'))
          .join('\n');
        log('Pas de dossier drizzle/ détecté, lignes COPY drizzle retirées du Dockerfile.');
      }

      await fsp.writeFile(dockerfilePath, dockerfileContent, 'utf-8');
      log('Dockerfile généré.');

      // Commit le Dockerfile
      if (sourceType === 'zip') {
        await execCommand(
          `cd ${srcDir} && git config user.email "admin@santinova-soft.org" && git config user.name "Manus Dashboard" && git add Dockerfile && git commit -m "Add auto-generated Dockerfile" && git push origin main`,
          60000
        );
        log('Dockerfile commité et poussé sur Gitea.');
      }
    } else {
      log('Dockerfile existant détecté, utilisation du Dockerfile du projet.');
    }

    // 4. Générer le docker-compose.yml
    log('Génération du docker-compose.yml...');
    const containerPort = getContainerPort(stack, port);
    const composeContent = generateDockerCompose({
      appId,
      subdomain,
      stack,
      port: containerPort,
      needsDb: needsDb || false,
      dbType: dbType || 'mysql',
    });
    await fsp.writeFile(path.join(appDir, 'docker-compose.yml'), composeContent, 'utf-8');
    log('docker-compose.yml généré.');

    // 5. Lancer le build et le déploiement Docker
    log('Lancement du build Docker (cela peut prendre quelques minutes)...');
    const buildResult = await execCommand(
      `cd ${appDir} && docker compose build --no-cache 2>&1`,
      600000
    );
    log('Build Docker terminé.');

    log('Démarrage des conteneurs...');
    await execCommand(`cd ${appDir} && docker compose up -d 2>&1`, 120000);
    log('Conteneurs démarrés.');

    // 6. Créer un utilisateur admin dans la base de données si nécessaire
    if (needsDb) {
      log('Attente du démarrage de la base de données (15s)...');
      await new Promise((resolve) => setTimeout(resolve, 15000));

      try {
        log('Tentative de création de l\'utilisateur admin dans la base de données...');
        if (dbType === 'postgres') {
          await execCommand(
            `docker exec ${appId}-db psql -U app_user -d ${appId.replace(/-/g, '_')} -c "CREATE TABLE IF NOT EXISTS admin_users (id SERIAL PRIMARY KEY, email VARCHAR(255), password VARCHAR(255), role VARCHAR(50), created_at TIMESTAMP DEFAULT NOW()); INSERT INTO admin_users (email, password, role) VALUES ('adminItinova@santinova-soft.org', 'Itinova69!', 'admin') ON CONFLICT DO NOTHING;" 2>&1`,
            30000
          );
        } else {
          await execCommand(
            `docker exec ${appId}-db mysql -uapp_user -pAppPassword2026! ${appId.replace(/-/g, '_')} -e "CREATE TABLE IF NOT EXISTS admin_users (id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255), password VARCHAR(255), role VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP); INSERT IGNORE INTO admin_users (email, password, role) VALUES ('adminItinova@santinova-soft.org', 'Itinova69!', 'admin');" 2>&1`,
            30000
          );
        }
        log('Utilisateur admin créé dans la base de données.');
      } catch (dbErr) {
        log(`Note: Création de l'utilisateur admin en BDD non critique: ${dbErr.message}`);
      }
    }

    // 7. Enregistrer l'application dans la configuration dynamique
    const appDef = {
      id: appId,
      name: name,
      description: description || `Application ${name}`,
      directory: appId,
      giteaRepo: giteaRepoName,
      giteaOwner: giteaOwner,
      urls: {
        recette: `https://${domain}`,
        prod: null,
      },
      containerName: appId,
      healthCheckUrl: `https://${domain}`,
      port: containerPort,
      stack: stack,
      needsDb: needsDb || false,
      dbType: dbType || null,
      createdAt: new Date().toISOString(),
    };

    addDynamicApp(appDef);
    log(`Application ${name} enregistrée dans le dashboard.`);
    log(`Accessible sur: https://${domain}`);

    return {
      success: true,
      app: appDef,
      message: `Application ${name} créée et déployée avec succès`,
      url: `https://${domain}`,
    };
  } catch (err) {
    log(`ERREUR: ${err.message}`);
    throw err;
  }
}

function execCommand(cmd, timeout = 60000) {
  return new Promise((resolve, reject) => {
    exec(cmd, { maxBuffer: 1024 * 1024 * 50, timeout }, (error, stdout, stderr) => {
      if (error) {
        reject(new Error(`Commande échouée: ${error.message}\nStdout: ${stdout}\nStderr: ${stderr}`));
      } else {
        resolve(stdout + stderr);
      }
    });
  });
}

// ============ STACK DETECTION ============

function getAvailableStacks() {
  return [
    { id: 'nodejs', name: 'Node.js', icon: 'nodejs', description: 'Application Node.js (Express, Fastify, etc.)' },
    { id: 'nodejs-next', name: 'Node.js (Next.js)', icon: 'nodejs', description: 'Application Next.js avec SSR' },
    { id: 'python', name: 'Python', icon: 'python', description: 'Application Python générique' },
    { id: 'python-flask', name: 'Python (Flask)', icon: 'python', description: 'Application Flask' },
    { id: 'python-django', name: 'Python (Django)', icon: 'python', description: 'Application Django' },
    { id: 'php', name: 'PHP', icon: 'php', description: 'Application PHP avec Apache' },
    { id: 'php-laravel', name: 'PHP (Laravel)', icon: 'php', description: 'Application Laravel' },
    { id: 'static', name: 'Site statique', icon: 'static', description: 'HTML/CSS/JS statique (Nginx)' },
    { id: 'static-react', name: 'React (SPA)', icon: 'react', description: 'Application React avec build (Vite/CRA)' },
  ];
}

module.exports = {
  createApplication,
  generateDockerCompose,
  getAvailableStacks,
  initDynamicApps,
  loadDynamicApps,
  addDynamicApp,
  DOCKERFILE_TEMPLATES,
};
