import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { serveStatic } from 'hono/bun';
import { streamSSE } from 'hono/streaming';
import { createProductionDokployClient } from './api/dokploy-production.js';
import { ProductionDeployer } from './orchestrator/production-deployer.js';
import type { DeploymentState as OrchestratorDeploymentState } from './orchestrator/production-deployer.js';

const PORT = parseInt(process.env.PORT || '3000', 10);
const HOST = process.env.HOST || '0.0.0.0';

// Extended deployment state for HTTP server (adds logs and language)
interface HttpDeploymentState extends OrchestratorDeploymentState {
  logs: string[];
  lang: string;
}

const deployments = new Map<string, HttpDeploymentState>();

// Generate a unique deployment ID
function generateDeploymentId(): string {
  return `dep_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
}

// Validate stack name
function validateStackName(name: string): { valid: boolean; error?: string } {
  if (!name || typeof name !== 'string') {
    return { valid: false, error: 'Name is required' };
  }

  const trimmedName = name.trim().toLowerCase();

  if (trimmedName.length < 3 || trimmedName.length > 20) {
    return { valid: false, error: 'Name must be between 3 and 20 characters' };
  }

  if (!/^[a-z0-9-]+$/.test(trimmedName)) {
    return { valid: false, error: 'Name can only contain lowercase letters, numbers, and hyphens' };
  }

  if (trimmedName.startsWith('-') || trimmedName.endsWith('-')) {
    return { valid: false, error: 'Name cannot start or end with a hyphen' };
  }

  const reservedNames = (process.env.RESERVED_NAMES || 'admin,api,www,root,system,test,demo,portal').split(',');
  if (reservedNames.includes(trimmedName)) {
    return { valid: false, error: `Name "${trimmedName}" is reserved` };
  }

  return { valid: true };
}

// Main deployment orchestration using production components
async function deployStack(deploymentId: string): Promise<void> {
  const deployment = deployments.get(deploymentId);
  if (!deployment) {
    throw new Error('Deployment not found');
  }

  try {
    const client = createProductionDokployClient();

    // Progress callback to update state in real-time
    const progressCallback = (state: OrchestratorDeploymentState) => {
      const currentDeployment = deployments.get(deploymentId);
      if (currentDeployment) {
        // Update all fields from orchestrator state
        currentDeployment.phase = state.phase;
        currentDeployment.status = state.status;
        currentDeployment.progress = state.progress;
        currentDeployment.message = state.message;
        currentDeployment.url = state.url;
        currentDeployment.error = state.error;
        currentDeployment.resources = state.resources;
        currentDeployment.timestamps = state.timestamps;

        deployments.set(deploymentId, { ...currentDeployment });
      }
    };

    const deployer = new ProductionDeployer(client, progressCallback);

    const result = await deployer.deploy({
      stackName: deployment.stackName,
      dockerImage: process.env.STACK_IMAGE || 'git.app.flexinit.nl/flexinit/agent-stack:latest',
      domainSuffix: process.env.STACK_DOMAIN_SUFFIX || 'ai.flexinit.nl',
      port: 8080,
      healthCheckTimeout: 180000,
      healthCheckInterval: 5000,
      registryId: process.env.STACK_REGISTRY_ID,
      sharedProjectId: process.env.SHARED_PROJECT_ID,
      sharedEnvironmentId: process.env.SHARED_ENVIRONMENT_ID,
      lang: deployment.lang,
    });

    // Final update with logs
    const finalDeployment = deployments.get(deploymentId);
    if (finalDeployment) {
      finalDeployment.logs = result.logs;
      deployments.set(deploymentId, { ...finalDeployment });
    }

  } catch (error) {
    // Deployment failed catastrophically (before orchestrator could handle it)
    const currentDeployment = deployments.get(deploymentId);
    if (currentDeployment) {
      currentDeployment.status = 'failure';
      currentDeployment.phase = 'failed';
      currentDeployment.error = {
        phase: currentDeployment.phase,
        message: error instanceof Error ? error.message : 'Unknown error',
        code: 'DEPLOYMENT_FAILED',
      };
      currentDeployment.message = 'Deployment failed';
      currentDeployment.timestamps.completed = new Date().toISOString();
      deployments.set(deploymentId, { ...currentDeployment });
    }
    throw error;
  }
}

const app = new Hono();

app.use('*', logger());
app.use('*', cors());

app.get('/health', (c) => {
  return c.json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    version: '0.2.0', // Bumped version for production components
    service: 'ai-stack-deployer',
    activeDeployments: deployments.size,
    features: {
      productionClient: true,
      retryLogic: true,
      circuitBreaker: true,
      autoRollback: true,
      healthVerification: true,
    }
  });
});

// Deploy endpoint
app.post('/api/deploy', async (c) => {
  try {
    const body = await c.req.json();
    const { name, lang = 'en' } = body;

    // Validate name
    const validation = validateStackName(name);
    if (!validation.valid) {
      return c.json({
        success: false,
        error: validation.error,
        code: 'INVALID_NAME'
      }, 400);
    }

    const normalizedName = name.trim().toLowerCase();

    const client = createProductionDokployClient();
    const sharedEnvironmentId = process.env.SHARED_ENVIRONMENT_ID;
    const appName = `opencode-${normalizedName}`;

    if (sharedEnvironmentId) {
      const existingApp = await client.findApplicationByName(sharedEnvironmentId, appName);
      if (existingApp) {
        return c.json({
          success: false,
          error: 'Name already taken',
          code: 'NAME_EXISTS'
        }, 409);
      }
    } else {
      const projectName = `ai-stack-${normalizedName}`;
      const existingProject = await client.findProjectByName(projectName);
      if (existingProject) {
        return c.json({
          success: false,
          error: 'Name already taken',
          code: 'NAME_EXISTS'
        }, 409);
      }
    }

    // Create deployment state
    const deploymentId = generateDeploymentId();
    const deployment: HttpDeploymentState = {
      id: deploymentId,
      stackName: normalizedName,
      phase: 'initializing',
      status: 'in_progress',
      progress: 0,
      message: 'Initializing deployment',
      resources: {},
      timestamps: {
        started: new Date().toISOString(),
      },
      logs: [],
      lang,
    };

    deployments.set(deploymentId, deployment);

    // Start deployment in background
    deployStack(deploymentId).catch(err => {
      console.error(`Deployment ${deploymentId} failed:`, err);
    });

    return c.json({
      success: true,
      deploymentId,
      url: `https://${normalizedName}.${process.env.STACK_DOMAIN_SUFFIX || 'ai.flexinit.nl'}`,
      statusEndpoint: `/api/status/${deploymentId}`
    });

  } catch (error) {
    console.error('Deploy endpoint error:', error);
    return c.json({
      success: false,
      error: error instanceof Error ? error.message : 'Internal server error',
      code: 'INTERNAL_ERROR'
    }, 500);
  }
});

// Status endpoint with SSE
app.get('/api/status/:deploymentId', (c) => {
  const deploymentId = c.req.param('deploymentId');
  const deployment = deployments.get(deploymentId);

  if (!deployment) {
    return c.json({
      success: false,
      error: 'Deployment not found',
      code: 'NOT_FOUND'
    }, 404);
  }

  return streamSSE(c, async (stream) => {
    let lastStatus = '';

    try {
      // Stream updates until deployment completes or fails
      while (true) {
        const currentDeployment = deployments.get(deploymentId);

        if (!currentDeployment) {
          await stream.writeSSE({
            event: 'error',
            data: JSON.stringify({ message: 'Deployment not found' })
          });
          break;
        }

        // Send update if status changed
        const currentStatus = JSON.stringify({
          phase: currentDeployment.phase,
          status: currentDeployment.status,
          progress: currentDeployment.progress,
          message: currentDeployment.message,
        });

        if (currentStatus !== lastStatus) {
          await stream.writeSSE({
            event: 'progress',
            data: JSON.stringify({
              phase: currentDeployment.phase,
              status: currentDeployment.status,
              progress: currentDeployment.progress,
              message: currentDeployment.message,
              currentStep: currentDeployment.message, // Backward compatibility
              url: currentDeployment.url,
              error: currentDeployment.error?.message,
              resources: currentDeployment.resources,
            })
          });
          lastStatus = currentStatus;
        }

        // Exit if terminal state
        if (currentDeployment.status === 'success' || currentDeployment.phase === 'completed') {
          await stream.writeSSE({
            event: 'complete',
            data: JSON.stringify({
              url: currentDeployment.url,
              status: 'ready',
              resources: currentDeployment.resources,
              duration: currentDeployment.timestamps.completed && currentDeployment.timestamps.started
                ? (new Date(currentDeployment.timestamps.completed).getTime() -
                   new Date(currentDeployment.timestamps.started).getTime()) / 1000
                : null,
            })
          });
          break;
        }

        if (currentDeployment.status === 'failure' || currentDeployment.phase === 'failed') {
          await stream.writeSSE({
            event: 'error',
            data: JSON.stringify({
              message: currentDeployment.error?.message || 'Deployment failed',
              status: 'failed',
              phase: currentDeployment.error?.phase,
              code: currentDeployment.error?.code,
            })
          });
          break;
        }

        // Wait before next check
        await stream.sleep(1000);
      }
    } catch (error) {
      console.error('SSE stream error:', error);
    }
  });
});

// Get deployment details (new endpoint for debugging)
app.get('/api/deployment/:deploymentId', (c) => {
  const deploymentId = c.req.param('deploymentId');
  const deployment = deployments.get(deploymentId);

  if (!deployment) {
    return c.json({
      success: false,
      error: 'Deployment not found',
      code: 'NOT_FOUND'
    }, 404);
  }

  return c.json({
    success: true,
    deployment: {
      id: deployment.id,
      stackName: deployment.stackName,
      phase: deployment.phase,
      status: deployment.status,
      progress: deployment.progress,
      message: deployment.message,
      url: deployment.url,
      error: deployment.error,
      resources: deployment.resources,
      timestamps: deployment.timestamps,
      logs: deployment.logs.slice(-50), // Last 50 log entries
    }
  });
});

// Check name availability
app.get('/api/check/:name', async (c) => {
  try {
    const name = c.req.param('name');

    const validation = validateStackName(name);
    if (!validation.valid) {
      return c.json({
        available: false,
        valid: false,
        error: validation.error
      });
    }

    const normalizedName = name.trim().toLowerCase();
    const client = createProductionDokployClient();
    const sharedEnvironmentId = process.env.SHARED_ENVIRONMENT_ID;

    let exists = false;
    if (sharedEnvironmentId) {
      const appName = `opencode-${normalizedName}`;
      const existingApp = await client.findApplicationByName(sharedEnvironmentId, appName);
      exists = !!existingApp;
    } else {
      const projectName = `ai-stack-${normalizedName}`;
      const existingProject = await client.findProjectByName(projectName);
      exists = !!existingProject;
    }

    return c.json({
      available: !exists,
      valid: true,
      name: normalizedName
    });

  } catch (error) {
    console.error('Check endpoint error:', error);
    return c.json({
      available: false,
      valid: false,
      error: 'Failed to check availability'
    }, 500);
  }
});

app.delete('/api/stack/:name', async (c) => {
  try {
    const name = c.req.param('name');
    const normalizedName = name.trim().toLowerCase();
    const client = createProductionDokployClient();
    const sharedEnvironmentId = process.env.SHARED_ENVIRONMENT_ID;

    if (sharedEnvironmentId) {
      const appName = `opencode-${normalizedName}`;
      const existingApp = await client.findApplicationByName(sharedEnvironmentId, appName);

      if (!existingApp) {
        return c.json({
          success: false,
          error: 'Stack not found',
          code: 'NOT_FOUND'
        }, 404);
      }

      console.log(`Deleting stack: ${appName} (applicationId: ${existingApp.applicationId})`);
      await client.deleteApplication(existingApp.applicationId);

      return c.json({
        success: true,
        message: `Stack ${normalizedName} deleted successfully`,
        deletedApplicationId: existingApp.applicationId
      });
    } else {
      const projectName = `ai-stack-${normalizedName}`;
      const existingProject = await client.findProjectByName(projectName);

      if (!existingProject) {
        return c.json({
          success: false,
          error: 'Stack not found',
          code: 'NOT_FOUND'
        }, 404);
      }

      console.log(`Deleting stack: ${projectName} (projectId: ${existingProject.project.projectId})`);
      await client.deleteProject(existingProject.project.projectId);

      return c.json({
        success: true,
        message: `Stack ${normalizedName} deleted successfully`,
        deletedProjectId: existingProject.project.projectId
      });
    }

  } catch (error) {
    console.error('Delete endpoint error:', error);
    return c.json({
      success: false,
      error: error instanceof Error ? error.message : 'Failed to delete stack',
      code: 'DELETE_FAILED'
    }, 500);
  }
});

const REACT_BUILD_PATH = './dist/client';
const LEGACY_FRONTEND_PATH = './src/frontend';
const USE_REACT = process.env.USE_REACT_FRONTEND !== 'false';

async function serveFile(reactPath: string, legacyPath: string, contentType: string) {
  const filePath = USE_REACT ? `${REACT_BUILD_PATH}${reactPath}` : `${LEGACY_FRONTEND_PATH}${legacyPath}`;
  const file = Bun.file(filePath);
  if (await file.exists()) {
    return new Response(file, { headers: { 'Content-Type': contentType } });
  }
  const fallbackFile = Bun.file(`${LEGACY_FRONTEND_PATH}${legacyPath}`);
  if (await fallbackFile.exists()) {
    return new Response(fallbackFile, { headers: { 'Content-Type': contentType } });
  }
  return new Response('Not Found', { status: 404 });
}

app.get('/assets/*', async (c) => {
  const path = c.req.path;
  const file = Bun.file(`${REACT_BUILD_PATH}${path}`);
  if (await file.exists()) {
    const ext = path.split('.').pop();
    const contentTypes: Record<string, string> = {
      js: 'application/javascript',
      css: 'text/css',
      svg: 'image/svg+xml',
      png: 'image/png',
      jpg: 'image/jpeg',
      woff: 'font/woff',
      woff2: 'font/woff2',
    };
    return new Response(file, { 
      headers: { 'Content-Type': contentTypes[ext || ''] || 'application/octet-stream' } 
    });
  }
  return new Response('Not Found', { status: 404 });
});

app.get('/style.css', (c) => serveFile('/style.css', '/style.css', 'text/css'));
app.get('/app.js', (c) => serveFile('/app.js', '/app.js', 'application/javascript'));
app.get('/og-image.png', (c) => serveFile('/og-image.png', '/og-image.png', 'image/png'));
app.get('/favicon.svg', (c) => serveFile('/favicon.svg', '/favicon.svg', 'image/svg+xml'));
app.get('/favicon.ico', (c) => serveFile('/favicon.ico', '/favicon.ico', 'image/x-icon'));
app.get('/favicon.png', (c) => serveFile('/favicon.png', '/favicon.png', 'image/png'));
app.get('/apple-touch-icon.png', (c) => serveFile('/apple-touch-icon.png', '/apple-touch-icon.png', 'image/png'));

app.get('*', async (c) => {
  if (c.req.path.startsWith('/api/') || c.req.path === '/health') {
    return c.notFound();
  }
  
  const indexPath = USE_REACT ? `${REACT_BUILD_PATH}/index.html` : `${LEGACY_FRONTEND_PATH}/index.html`;
  const file = Bun.file(indexPath);
  if (await file.exists()) {
    return new Response(file, { headers: { 'Content-Type': 'text/html' } });
  }
  const fallback = Bun.file(`${LEGACY_FRONTEND_PATH}/index.html`);
  return new Response(fallback, { headers: { 'Content-Type': 'text/html' } });
});

console.log(`🚀 AI Stack Deployer (Production) starting on http://${HOST}:${PORT}`);
console.log(`✅ Production features enabled:`);
console.log(`   - Retry logic with exponential backoff`);
console.log(`   - Circuit breaker pattern`);
console.log(`   - Automatic rollback on failure`);
console.log(`   - Health verification`);
console.log(`   - Structured logging`);

export default {
  port: PORT,
  hostname: HOST,
  fetch: app.fetch,
  idleTimeout: 255,
};
