import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { Queue } from 'bullmq';
import Redis from 'ioredis';
import { prisma } from '../utils/prisma';
import { authenticate } from '../middleware/auth';
import { getWorkerConfig } from '@wcag-ada/config';
import type { AccessibilityScanOptions } from '@wcag-ada/shared';

const scanRoutes = new Hono();

// Get worker config for queue connection
const workerConfig = getWorkerConfig();

// Create queue connection
const scanQueue = new Queue('accessibility-scans', {
  connection: new Redis({
    host: workerConfig.redis.host,
    port: workerConfig.redis.port,
    password: workerConfig.redis.password,
    db: workerConfig.redis.db,
  }),
});

// Apply authentication to all routes
scanRoutes.use('*', authenticate);

// Validation schemas
const createScanSchema = z.object({
  websiteId: z.string(),
  url: z.string().url().optional(),
  options: z.object({
    wcagLevel: z.object({
      level: z.enum(['A', 'AA', 'AAA']),
      version: z.enum(['2.0', '2.1', '2.2']),
    }).optional(),
    viewport: z.object({
      width: z.number(),
      height: z.number(),
      deviceScaleFactor: z.number().optional(),
      isMobile: z.boolean().optional(),
    }).optional(),
    includeScreenshots: z.boolean().optional(),
    excludeSelectors: z.array(z.string()).optional(),
    waitForSelector: z.string().optional(),
    timeout: z.number().optional(),
  }).optional(),
});

// Start a new scan
scanRoutes.post('/', zValidator('json', createScanSchema), async (c) => {
  const userId = c.get('userId');
  const { websiteId, url, options } = c.req.valid('json');
  
  // Get website
  const website = await prisma.website.findFirst({
    where: {
      id: websiteId,
      userId,
      active: true,
    },
  });
  
  if (!website) {
    return c.json({ error: 'Website not found' }, 404);
  }
  
  // Merge options with website defaults
  const scanOptions: AccessibilityScanOptions = {
    url: url || website.url,
    ...website.scanOptions as any,
    ...options,
    authenticate: website.authConfig as any,
  };
  
  // Create scan job
  const job = await prisma.scanJob.create({
    data: {
      websiteId,
      userId,
      url: scanOptions.url,
      options: scanOptions as any,
      status: 'PENDING',
    },
  });
  
  // Queue the scan
  await scanQueue.add('scan', {
    jobId: job.id,
    websiteId,
    userId,
    options: scanOptions,
  }, {
    jobId: job.id,
  });
  
  return c.json({
    job: {
      id: job.id,
      status: job.status,
      url: job.url,
      scheduledAt: job.scheduledAt,
    },
    message: 'Scan queued successfully',
  }, 202);
});

// Get scan status
scanRoutes.get('/:id', async (c) => {
  const userId = c.get('userId');
  const scanId = c.req.param('id');
  
  const job = await prisma.scanJob.findFirst({
    where: {
      id: scanId,
      userId,
    },
    include: {
      result: {
        select: {
          id: true,
          summary: true,
          wcagCompliance: true,
          createdAt: true,
        },
      },
    },
  });
  
  if (!job) {
    return c.json({ error: 'Scan not found' }, 404);
  }
  
  return c.json(job);
});

// Get scan result
scanRoutes.get('/:id/result', async (c) => {
  const userId = c.get('userId');
  const scanId = c.req.param('id');
  
  const result = await prisma.scanResult.findFirst({
    where: {
      jobId: scanId,
      job: {
        userId,
      },
    },
  });
  
  if (!result) {
    return c.json({ error: 'Scan result not found' }, 404);
  }
  
  return c.json(result);
});

// Get scan violations
scanRoutes.get('/:id/violations', async (c) => {
  const userId = c.get('userId');
  const scanId = c.req.param('id');
  const { impact, tag } = c.req.query();
  
  const result = await prisma.scanResult.findFirst({
    where: {
      jobId: scanId,
      job: {
        userId,
      },
    },
    select: {
      violations: true,
    },
  });
  
  if (!result) {
    return c.json({ error: 'Scan result not found' }, 404);
  }
  
  let violations = result.violations as any[];
  
  // Filter by impact if provided
  if (impact) {
    violations = violations.filter(v => v.impact === impact);
  }
  
  // Filter by tag if provided
  if (tag) {
    violations = violations.filter(v => v.tags.includes(tag));
  }
  
  return c.json({ violations });
});

// Cancel a pending scan
scanRoutes.delete('/:id', async (c) => {
  const userId = c.get('userId');
  const scanId = c.req.param('id');
  
  const job = await prisma.scanJob.findFirst({
    where: {
      id: scanId,
      userId,
      status: 'PENDING',
    },
  });
  
  if (!job) {
    return c.json({ error: 'Scan not found or cannot be cancelled' }, 404);
  }
  
  // Update job status
  await prisma.scanJob.update({
    where: { id: scanId },
    data: {
      status: 'FAILED',
      error: 'Cancelled by user',
      completedAt: new Date(),
    },
  });
  
  // Remove from queue if possible
  const queueJob = await scanQueue.getJob(scanId);
  if (queueJob) {
    await queueJob.remove();
  }
  
  return c.json({ message: 'Scan cancelled successfully' });
});

// Get user's scan history
scanRoutes.get('/', async (c) => {
  const userId = c.get('userId');
  const { page = '1', limit = '20', status, websiteId } = c.req.query();
  
  const pageNum = parseInt(page);
  const limitNum = parseInt(limit);
  const skip = (pageNum - 1) * limitNum;
  
  const where = {
    userId,
    ...(status && { status }),
    ...(websiteId && { websiteId }),
  };
  
  const [jobs, total] = await Promise.all([
    prisma.scanJob.findMany({
      where,
      skip,
      take: limitNum,
      orderBy: { createdAt: 'desc' },
      include: {
        website: {
          select: {
            id: true,
            name: true,
            url: true,
          },
        },
        result: {
          select: {
            id: true,
            summary: true,
          },
        },
      },
    }),
    prisma.scanJob.count({ where }),
  ]);
  
  return c.json({
    scans: jobs,
    pagination: {
      page: pageNum,
      limit: limitNum,
      total,
      totalPages: Math.ceil(total / limitNum),
    },
  });
});

export { scanRoutes };