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)
interface HttpDeploymentState extends OrchestratorDeploymentState {
  logs: 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);

    // Execute deployment with production orchestrator
    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,
    });

    // 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 } = 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();

    // Check if name is already taken
    const client = createProductionDokployClient();
    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: [],
    };

    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');

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

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

    // Check if project exists
    const client = createProductionDokployClient();
    const projectName = `ai-stack-${normalizedName}`;
    const existingProject = await client.findProjectByName(projectName);

    return c.json({
      available: !existingProject,
      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 projectName = `ai-stack-${normalizedName}`;

    const client = createProductionDokployClient();
    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);
  }
});

// Serve static files (frontend)
// Serve CSS and JS files directly
app.get('/style.css', async (c) => {
  const file = Bun.file('./src/frontend/style.css');
  return new Response(file, {
    headers: { 'Content-Type': 'text/css' }
  });
});

app.get('/app.js', async (c) => {
  const file = Bun.file('./src/frontend/app.js');
  return new Response(file, {
    headers: { 'Content-Type': 'application/javascript' }
  });
});

app.get('/og-image.png', async (c) => {
  const file = Bun.file('./src/frontend/og-image.png');
  return new Response(file, {
    headers: { 'Content-Type': 'image/png' }
  });
});

app.get('/favicon.svg', async (c) => {
  const file = Bun.file('./src/frontend/favicon.svg');
  return new Response(file, {
    headers: { 'Content-Type': 'image/svg+xml' }
  });
});

app.get('/favicon.ico', async (c) => {
  const file = Bun.file('./src/frontend/favicon.ico');
  return new Response(file, {
    headers: { 'Content-Type': 'image/x-icon' }
  });
});

app.get('/favicon.png', async (c) => {
  const file = Bun.file('./src/frontend/favicon.png');
  return new Response(file, {
    headers: { 'Content-Type': 'image/png' }
  });
});

app.get('/apple-touch-icon.png', async (c) => {
  const file = Bun.file('./src/frontend/apple-touch-icon.png');
  return new Response(file, {
    headers: { 'Content-Type': 'image/png' }
  });
});

// Serve index.html for all other routes (SPA fallback)
app.get('/', async (c) => {
  const file = Bun.file('./src/frontend/index.html');
  return new Response(file, {
    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,
};
