import db from '../db';
import { Snapshot } from '../types';
import { fetchPage } from './fetcher';
import {
  applyIgnoreRules,
  applyCommonNoiseFilters,
  compareDiffs,
  checkKeywords,
} from './differ';
import { calculateChangeImportance } from './importance';
import { sendChangeAlert, sendErrorAlert, sendKeywordAlert } from './alerter';
import { generateSimpleSummary, generateAISummary } from './summarizer';
import { processSeoChecks } from './seo';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6380', {
  maxRetriesPerRequest: null,
});

async function acquireLock(key: string, ttlMs = 120000): Promise<boolean> {
  const result = await redis.set(key, '1', 'PX', ttlMs, 'NX');
  return result === 'OK';
}

async function releaseLock(key: string): Promise<void> {
  await redis.del(key);
}

export interface CheckResult {
  snapshot: Snapshot;
  alertSent: boolean;
}

export async function checkMonitor(
  monitorId: string,
  forceSeo = false,
  checkType: 'all' | 'content' | 'seo' = 'all'
): Promise<{ snapshot?: Snapshot; alertSent: boolean } | void> {
  console.log(`[Monitor] Starting check: ${monitorId} | Type: ${checkType} | ForceSEO: ${forceSeo}`);

  const lockKey = `lock:monitor-check:${monitorId}`;
  const acquired = await acquireLock(lockKey);
  if (!acquired) {
    console.log(`[Monitor] Skipping ${monitorId} - another check is already running`);
    return;
  }

  try {
    const monitor = await db.monitors.findById(monitorId);

    if (!monitor) {
      console.error(`[Monitor] Monitor ${monitorId} not found`);
      return;
    }

    if (monitor.status !== 'active' && monitor.status !== 'error') {
      console.log(`[Monitor] Monitor ${monitorId} skipping (status: ${monitor.status})`);
      return;
    }

    let snapshot: Snapshot | undefined;
    let changed = false;

    // Content Check Part
    if (checkType === 'all' || checkType === 'content') {
      console.log(`[Monitor] Running CONTENT check for ${monitor.name} (${monitor.url})`);
      // Fetch page with retries
      let fetchResult = await fetchPage(monitor.url, monitor.elementSelector);

      // Retry on failure (max 3 attempts)
      if (fetchResult.error) {
        console.log(`[Monitor] Fetch failed, retrying... (1/3)`);
        await new Promise((resolve) => setTimeout(resolve, 2000));
        fetchResult = await fetchPage(monitor.url, monitor.elementSelector);

        if (fetchResult.error) {
          console.log(`[Monitor] Fetch failed, retrying... (2/3)`);
          await new Promise((resolve) => setTimeout(resolve, 2000));
          fetchResult = await fetchPage(monitor.url, monitor.elementSelector);
        }
      }

      // Check for HTTP error status
      if (!fetchResult.error && fetchResult.status >= 400) {
        fetchResult.error = `HTTP ${fetchResult.status}`;
      }

      // If still failing after retries
      if (fetchResult.error) {
        console.error(`[Monitor] Failed to fetch ${monitor.url}: ${fetchResult.error}`);

        // Create error snapshot
        const failedSnapshot = await db.snapshots.create({
          monitorId: monitor.id,
          htmlContent: '',
          textContent: '',
          contentHash: '',
          httpStatus: fetchResult.status,
          responseTime: fetchResult.responseTime,
          changed: false,
          errorMessage: fetchResult.error,
        });

        await db.monitors.incrementErrors(monitor.id);

        // Send error alert if consecutive errors > 3
        if (monitor.consecutiveErrors >= 2) {
          const user = await db.users.findById(monitor.userId);
          if (user) {
            await sendErrorAlert(monitor, user, fetchResult.error);
          }
        }

        return { snapshot: failedSnapshot, alertSent: false };
      }

      // Apply noise filters
      let processedHtml = applyCommonNoiseFilters(fetchResult.html);
      processedHtml = applyIgnoreRules(processedHtml, monitor.ignoreRules);

      // Get previous snapshot
      const previousSnapshot = await db.snapshots.findLatestByMonitorId(monitor.id);

      let changePercentage = 0;
      let diffResult: ReturnType<typeof compareDiffs> | undefined;

      if (previousSnapshot) {
        // Apply same filters to previous content for fair comparison
        let previousHtml = applyCommonNoiseFilters(previousSnapshot.htmlContent);
        previousHtml = applyIgnoreRules(previousHtml, monitor.ignoreRules);

        // Compare
        diffResult = compareDiffs(previousHtml, processedHtml);
        changed = diffResult.changed;
        changePercentage = diffResult.changePercentage;

        console.log(
          `[Monitor] ${monitor.name}: Changed=${changed}, Change%=${changePercentage.toFixed(2)}, Additions=${diffResult.additions}, Deletions=${diffResult.deletions}`
        );

        // Check keywords
        if (monitor.keywordRules && monitor.keywordRules.length > 0) {
          const keywordMatches = checkKeywords(
            previousHtml,
            processedHtml,
            monitor.keywordRules
          );

          if (keywordMatches.length > 0) {
            console.log(`[Monitor] Keyword matches found:`, keywordMatches);
            const user = await db.users.findById(monitor.userId);

            if (user) {
              for (const match of keywordMatches) {
                await sendKeywordAlert(monitor, user, match);
              }
            }
          }
        }
      } else {
        // First check - consider it as "changed" to create baseline
        changed = true;
        console.log(`[Monitor] First check for ${monitor.name}, creating baseline`);
      }

      // Generate human-readable summary (Hybrid approach)
      let summary: string | undefined;

      if (changed && previousSnapshot && diffResult) {
        // Hybrid logic: AI for changes (≥5%), simple for very small changes
        if (changePercentage >= 5) {
          console.log(`[Monitor] Change (${changePercentage}%), using AI summary`);
          try {
            summary = await generateAISummary(diffResult.diff, changePercentage, monitor.url);
          } catch (error) {
            console.error('[Monitor] AI summary failed, falling back to simple summary:', error);
            summary = generateSimpleSummary(
              diffResult.diff,
              previousSnapshot.htmlContent,
              fetchResult.html
            );
          }
        } else {
          console.log(`[Monitor] Small change (${changePercentage}%), using simple summary`);
          summary = generateSimpleSummary(
            diffResult.diff,
            previousSnapshot.htmlContent,
            fetchResult.html
          );
        }
      }

      // Create snapshot
      console.log(`[Monitor] Creating snapshot in DB for ${monitor.name}`);
      snapshot = await db.snapshots.create({
        monitorId: monitor.id,
        htmlContent: fetchResult.html,
        textContent: fetchResult.text,
        contentHash: fetchResult.hash,
        httpStatus: fetchResult.status,
        responseTime: fetchResult.responseTime,
        changed,
        changePercentage: changed ? changePercentage : undefined,
        importanceScore: changed ? await calculateChangeImportance(monitor.id, changePercentage, 0, processedHtml) : 0,
        summary,
      });

      // Update monitor
      await db.monitors.updateLastChecked(monitor.id, changed);

      // Send alert if changed and not first check
      if (changed && previousSnapshot) {
        try {
          const user = await db.users.findById(monitor.userId);
          if (user) {
            await sendChangeAlert(monitor, user, snapshot, changePercentage);
          }
        } catch (alertError) {
          console.error(`[Monitor] Failed to send alert for ${monitor.id}:`, alertError);
        }
      }
    }

    // SEO Check Part
    if ((checkType === 'all' || checkType === 'seo') && monitor.seoKeywords && monitor.seoKeywords.length > 0) {
      let shouldRunSeo = false;

      if (forceSeo) {
        console.log(`[Monitor] SEO check triggered manually for ${monitor.name}`);
        shouldRunSeo = true;
      } else if (monitor.seoInterval && monitor.seoInterval !== 'off') {
        if (!monitor.lastSeoCheckAt) {
          shouldRunSeo = true;
        } else {
          const hoursSinceLast = (Date.now() - new Date(monitor.lastSeoCheckAt).getTime()) / (1000 * 60 * 60);

          switch (monitor.seoInterval) {
            case 'daily': shouldRunSeo = hoursSinceLast >= 24; break;
            case '2d': shouldRunSeo = hoursSinceLast >= 48; break;
            case 'weekly': shouldRunSeo = hoursSinceLast >= 168; break;
            case 'monthly': shouldRunSeo = hoursSinceLast >= 720; break;
          }
        }
      }

      if (shouldRunSeo) {
        console.log(`[Monitor] Running SEO check for ${monitor.name} (Schedule: ${monitor.seoInterval})`);

        // Update last_seo_check_at immediately to prevent double scheduling if slow
        await db.monitors.update(monitor.id, { lastSeoCheckAt: new Date() });

        if (forceSeo) {
          // Await SEO check if explicitly forced (manual trigger)
          await processSeoChecks(monitor.id, monitor.url, monitor.seoKeywords);
        } else {
          // Run in background for scheduled checks
          processSeoChecks(monitor.id, monitor.url, monitor.seoKeywords)
            .catch(err => console.error(`[Monitor] SEO check failed for ${monitor.name}:`, err));
        }
      }
    }

    console.log(`[Monitor] Check completed for ${monitor.name} (Snapshot created: ${!!snapshot})`);
    return {
      snapshot,
      alertSent: changed
    };
  } catch (error) {
    console.error(`[Monitor] Error checking monitor ${monitorId}:`, error);
    await db.monitors.incrementErrors(monitorId);
  } finally {
    await releaseLock(lockKey);
  }
}

// Re-export scheduler functions for backward compatibility
export { scheduleMonitor, unscheduleMonitor, rescheduleMonitor } from './scheduler';
