/**
 * Enhanced Redirect Tracker Service for Redirect Intelligence v2
 * 
 * This service provides the new enhanced redirect tracking with database persistence
 * while maintaining backward compatibility with the legacy service.
 */

import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import https from 'https';
import { z } from 'zod';
import { prisma } from '../lib/prisma';
import { logger } from '../lib/logger';
import { CheckStatus, RedirectType } from '@prisma/client';

// Input validation schemas
const trackRequestSchema = z.object({
  url: z.string().url('Invalid URL format'),
  method: z.enum(['GET', 'POST', 'HEAD']).default('GET'),
  userAgent: z.string().optional(),
  headers: z.record(z.string()).optional(),
  projectId: z.string().optional(),
  followJS: z.boolean().default(false), // Future: JavaScript redirects
  maxHops: z.number().min(1).max(20).default(10),
  timeout: z.number().min(1000).max(30000).default(15000),
});

export type TrackRequest = z.infer<typeof trackRequestSchema>;

// Response interfaces
export interface HopResult {
  hopIndex: number;
  url: string;
  scheme?: string;
  statusCode?: number;
  redirectType: RedirectType;
  latencyMs?: number;
  contentType?: string;
  reason?: string;
  responseHeaders: Record<string, string>;
}

export interface CheckResult {
  id: string;
  inputUrl: string;
  method: string;
  status: CheckStatus;
  finalUrl?: string;
  totalTimeMs: number;
  startedAt: Date;
  finishedAt: Date;
  hops: HopResult[];
  redirectCount: number;
  loopDetected?: boolean;
  error?: string;
}

/**
 * Enhanced Redirect Tracker Service
 * 
 * Provides database-persisted redirect tracking with comprehensive analysis
 */
export class RedirectTrackerService {
  
  /**
   * Track redirects with database persistence
   */
  async trackUrl(request: TrackRequest, userId?: string): Promise<CheckResult> {
    const startTime = Date.now();
    const startedAt = new Date();
    
    // Validate input
    const validatedRequest = trackRequestSchema.parse(request);
    const { url: inputUrl, method, userAgent, headers = {}, projectId, maxHops, timeout } = validatedRequest;
    
    logger.info(`Starting enhanced redirect tracking: ${inputUrl}`, { 
      method, 
      projectId, 
      userId,
      maxHops 
    });

    // Create check record in database
    const check = await prisma.check.create({
      data: {
        projectId: projectId || 'anonymous-project', // Use anonymous project if none specified
        inputUrl,
        method,
        headersJson: headers,
        userAgent,
        startedAt,
        status: CheckStatus.OK, // Will be updated based on results
      }
    });

    try {
      // Perform redirect tracking
      const hops = await this.performRedirectChain(
        inputUrl,
        method,
        userAgent,
        headers,
        maxHops,
        timeout
      );

      const finishedAt = new Date();
      const totalTimeMs = finishedAt.getTime() - startedAt.getTime();
      
      // Analyze results
      const finalHop = hops[hops.length - 1];
      const finalUrl = finalHop?.url;
      const redirectCount = hops.length - 1;
      const loopDetected = this.detectRedirectLoop(hops);
      
      // Determine final status
      let status = CheckStatus.OK;
      if (loopDetected) {
        status = CheckStatus.LOOP;
      } else if (finalHop?.redirectType === RedirectType.OTHER && finalHop.reason?.includes('Error')) {
        status = CheckStatus.ERROR;
      } else if (totalTimeMs > timeout) {
        status = CheckStatus.TIMEOUT;
      }

      // Update check with results
      await prisma.check.update({
        where: { id: check.id },
        data: {
          finishedAt,
          status,
          finalUrl,
          totalTimeMs,
        }
      });

      // Save hops to database
      await this.saveHopsToDatabase(check.id, hops);

      const result: CheckResult = {
        id: check.id,
        inputUrl,
        method,
        status,
        finalUrl,
        totalTimeMs,
        startedAt,
        finishedAt,
        hops: hops.map(hop => ({
          hopIndex: hop.hopIndex,
          url: hop.url,
          scheme: hop.scheme,
          statusCode: hop.statusCode,
          redirectType: hop.redirectType,
          latencyMs: hop.latencyMs,
          contentType: hop.contentType,
          reason: hop.reason,
          responseHeaders: hop.responseHeaders,
        })),
        redirectCount,
        loopDetected,
      };

      logger.info(`Enhanced redirect tracking completed: ${inputUrl}`, {
        checkId: check.id,
        status,
        redirectCount,
        totalTimeMs,
        loopDetected
      });

      return result;

    } catch (error) {
      // Update check with error status
      const finishedAt = new Date();
      const totalTimeMs = finishedAt.getTime() - startedAt.getTime();
      
      await prisma.check.update({
        where: { id: check.id },
        data: {
          finishedAt,
          status: CheckStatus.ERROR,
          totalTimeMs,
        }
      });

      logger.error(`Enhanced redirect tracking failed: ${inputUrl}`, { 
        checkId: check.id, 
        error: error instanceof Error ? error.message : 'Unknown error' 
      });

      throw error;
    }
  }

  /**
   * Perform the actual redirect chain tracking
   */
  private async performRedirectChain(
    initialUrl: string,
    method: string,
    userAgent?: string,
    headers: Record<string, string> = {},
    maxHops: number = 10,
    timeout: number = 15000
  ): Promise<Array<HopResult & { hopIndex: number; latencyMs?: number; scheme?: string }>> {
    const hops: Array<HopResult & { hopIndex: number; latencyMs?: number; scheme?: string }> = [];
    const visitedUrls = new Set<string>();
    let currentUrl = initialUrl;
    let hopIndex = 0;

    while (hopIndex < maxHops) {
      const hopStartTime = Date.now();
      
      // Check for loops
      if (visitedUrls.has(currentUrl)) {
        hops.push({
          hopIndex,
          url: currentUrl,
          redirectType: RedirectType.OTHER,
          reason: 'Redirect loop detected',
          responseHeaders: {},
        });
        break;
      }
      
      visitedUrls.add(currentUrl);

      try {
        const parsedUrl = new URL(currentUrl);
        const scheme = parsedUrl.protocol.replace(':', '');

        // Configure request
        const config: AxiosRequestConfig = {
          method: hopIndex === 0 ? method : 'GET', // Follow redirects with GET
          url: currentUrl,
          maxRedirects: 0, // Handle redirects manually
          validateStatus: (status) => status >= 200 && status < 600,
          timeout,
          responseType: 'text',
          decompress: true,
          headers: {
            ...headers,
            ...(userAgent ? { 'User-Agent': userAgent } : {}),
          },
        };

        // Configure HTTPS agent for SSL analysis
        if (scheme === 'https') {
          config.httpsAgent = new https.Agent({
            rejectUnauthorized: false,
            checkServerIdentity: () => undefined,
          });
        }

        const response: AxiosResponse<string> = await axios(config);
        const hopEndTime = Date.now();
        const latencyMs = hopEndTime - hopStartTime;

        // Extract response details
        const statusCode = response.status;
        const contentType = response.headers['content-type'];
        const responseHeaders = response.headers as Record<string, string>;

        // Determine redirect type and next URL
        let redirectType: RedirectType;
        let nextUrl: string | null = null;

        if (statusCode >= 300 && statusCode < 400 && response.headers.location) {
          // HTTP redirect
          nextUrl = response.headers.location;
          
          // Resolve relative URLs
          if (!nextUrl.startsWith('http')) {
            const baseUrl = new URL(currentUrl);
            nextUrl = new URL(nextUrl, baseUrl.origin).href;
          }

          // Determine specific redirect type
          switch (statusCode) {
            case 301: redirectType = RedirectType.HTTP_301; break;
            case 302: redirectType = RedirectType.HTTP_302; break;
            case 307: redirectType = RedirectType.HTTP_307; break;
            case 308: redirectType = RedirectType.HTTP_308; break;
            default: redirectType = RedirectType.OTHER; break;
          }
        } else {
          // Final destination or error
          redirectType = RedirectType.FINAL;
        }

        // Add hop to results
        hops.push({
          hopIndex,
          url: currentUrl,
          scheme,
          statusCode,
          redirectType,
          latencyMs,
          contentType,
          responseHeaders,
        });

        // Continue to next URL or stop
        if (nextUrl) {
          currentUrl = nextUrl;
          hopIndex++;
        } else {
          break;
        }

      } catch (error: any) {
        const hopEndTime = Date.now();
        const latencyMs = hopEndTime - hopStartTime;

        // Add error hop
        hops.push({
          hopIndex,
          url: currentUrl,
          redirectType: RedirectType.OTHER,
          latencyMs,
          reason: `Error: ${error.message}`,
          responseHeaders: error.response?.headers || {},
          statusCode: error.response?.status,
        });

        break;
      }
    }

    // If we hit max hops, mark as OTHER
    if (hopIndex >= maxHops) {
      const lastHop = hops[hops.length - 1];
      if (lastHop && lastHop.redirectType !== RedirectType.FINAL) {
        lastHop.redirectType = RedirectType.OTHER;
        lastHop.reason = `Max hops (${maxHops}) reached`;
      }
    }

    return hops;
  }

  /**
   * Save hops to database
   */
  private async saveHopsToDatabase(checkId: string, hops: Array<HopResult & { hopIndex: number; latencyMs?: number; scheme?: string }>): Promise<void> {
    await prisma.hop.createMany({
      data: hops.map(hop => ({
        checkId,
        hopIndex: hop.hopIndex,
        url: hop.url,
        scheme: hop.scheme,
        statusCode: hop.statusCode,
        redirectType: hop.redirectType,
        latencyMs: hop.latencyMs,
        contentType: hop.contentType,
        reason: hop.reason,
        responseHeadersJson: hop.responseHeaders,
      }))
    });
  }

  /**
   * Detect redirect loops
   */
  private detectRedirectLoop(hops: Array<{ url: string }>): boolean {
    const urlCounts = new Map<string, number>();
    
    for (const hop of hops) {
      const count = urlCounts.get(hop.url) || 0;
      urlCounts.set(hop.url, count + 1);
      
      if (count > 0) {
        return true; // URL visited more than once = loop
      }
    }
    
    return false;
  }

  /**
   * Get check by ID with hops
   */
  async getCheck(checkId: string, userId?: string): Promise<CheckResult | null> {
    const check = await prisma.check.findUnique({
      where: { id: checkId },
      include: {
        hops: {
          orderBy: { hopIndex: 'asc' }
        },
        project: {
          select: {
            id: true,
            name: true,
            orgId: true,
          }
        }
      }
    });

    if (!check) {
      return null;
    }

    // TODO: Add permission checking in future phases
    // For now, allow access to anonymous checks and user's own checks

    return {
      id: check.id,
      inputUrl: check.inputUrl,
      method: check.method,
      status: check.status,
      finalUrl: check.finalUrl || undefined,
      totalTimeMs: check.totalTimeMs || 0,
      startedAt: check.startedAt,
      finishedAt: check.finishedAt || check.startedAt,
      hops: check.hops.map(hop => ({
        hopIndex: hop.hopIndex,
        url: hop.url,
        scheme: hop.scheme || undefined,
        statusCode: hop.statusCode || undefined,
        redirectType: hop.redirectType,
        latencyMs: hop.latencyMs || undefined,
        contentType: hop.contentType || undefined,
        reason: hop.reason || undefined,
        responseHeaders: (hop.responseHeadersJson as Record<string, string>) || {},
      })),
      redirectCount: check.hops.length - 1,
      loopDetected: this.detectRedirectLoop(check.hops),
    };
  }

  /**
   * List checks for a project
   */
  async listChecks(projectId: string, limit: number = 50, offset: number = 0): Promise<CheckResult[]> {
    const checks = await prisma.check.findMany({
      where: { projectId },
      include: {
        hops: {
          orderBy: { hopIndex: 'asc' }
        }
      },
      orderBy: { startedAt: 'desc' },
      take: limit,
      skip: offset,
    });

    return checks.map(check => ({
      id: check.id,
      inputUrl: check.inputUrl,
      method: check.method,
      status: check.status,
      finalUrl: check.finalUrl || undefined,
      totalTimeMs: check.totalTimeMs || 0,
      startedAt: check.startedAt,
      finishedAt: check.finishedAt || check.startedAt,
      hops: check.hops.map(hop => ({
        hopIndex: hop.hopIndex,
        url: hop.url,
        scheme: hop.scheme || undefined,
        statusCode: hop.statusCode || undefined,
        redirectType: hop.redirectType,
        latencyMs: hop.latencyMs || undefined,
        contentType: hop.contentType || undefined,
        reason: hop.reason || undefined,
        responseHeaders: (hop.responseHeadersJson as Record<string, string>) || {},
      })),
      redirectCount: check.hops.length - 1,
      loopDetected: this.detectRedirectLoop(check.hops),
    }));
  }
}
