import { v4 as uuidv4 } from 'uuid';
import jwt from 'jsonwebtoken';
import { redis } from '../config/redis';
import { config } from '../config';
import { logger } from '../utils/logger';
import type { ScanTokenData, ScanLoginStatus } from '@gala/shared/types';

const SCAN_TOKEN_PREFIX = 'scan:';
const SESSION_TOKEN_PREFIX = 'session:';
const SCAN_TOKEN_TTL = 5 * 60; // 5 minutes
const SESSION_TOKEN_TTL = 24 * 60 * 60; // 24 hours

class ScanLoginService {
  /**
   * Generate a new scan token for QR code login
   */
  async generateScanToken(pcSocketId: string): Promise<{
    success: boolean;
    data?: { scanToken: string; qrCodeUrl: string; expiresAt: number };
    error?: string;
  }> {
    try {
      const scanToken = uuidv4();
      const now = Date.now();
      const expiresAt = now + SCAN_TOKEN_TTL * 1000;

      const tokenData: ScanTokenData = {
        scanToken,
        pcSocketId,
        status: 'pending',
        createdAt: now,
        expiresAt,
      };

      await redis.setex(
        `${SCAN_TOKEN_PREFIX}${scanToken}`,
        SCAN_TOKEN_TTL,
        JSON.stringify(tokenData)
      );

      // Generate QR code URL (mobile client will access this)
      const baseUrl = config.mobileClientUrl || 'http://localhost:5174';
      const qrCodeUrl = `${baseUrl}/scan-login?token=${scanToken}`;

      logger.info({ scanToken, pcSocketId }, 'Scan token generated');

      return {
        success: true,
        data: { scanToken, qrCodeUrl, expiresAt },
      };
    } catch (error) {
      logger.error({ error }, 'Failed to generate scan token');
      return { success: false, error: 'Failed to generate scan token' };
    }
  }

  /**
   * Validate a scan token
   */
  async validateToken(scanToken: string): Promise<{
    success: boolean;
    data?: { valid: boolean; status: ScanLoginStatus; expiresAt: number };
    error?: string;
  }> {
    try {
      const data = await redis.get(`${SCAN_TOKEN_PREFIX}${scanToken}`);

      if (!data) {
        return {
          success: true,
          data: { valid: false, status: 'expired', expiresAt: 0 },
        };
      }

      const tokenData: ScanTokenData = JSON.parse(data);

      if (Date.now() > tokenData.expiresAt) {
        await redis.del(`${SCAN_TOKEN_PREFIX}${scanToken}`);
        return {
          success: true,
          data: { valid: false, status: 'expired', expiresAt: 0 },
        };
      }

      return {
        success: true,
        data: {
          valid: true,
          status: tokenData.status,
          expiresAt: tokenData.expiresAt,
        },
      };
    } catch (error) {
      logger.error({ error, scanToken }, 'Failed to validate token');
      return { success: false, error: 'Failed to validate token' };
    }
  }

  /**
   * Mark token as scanned (mobile user has scanned the QR code)
   */
  async markScanned(scanToken: string): Promise<{
    success: boolean;
    pcSocketId?: string;
    error?: string;
  }> {
    try {
      const data = await redis.get(`${SCAN_TOKEN_PREFIX}${scanToken}`);

      if (!data) {
        return { success: false, error: 'Token not found or expired' };
      }

      const tokenData: ScanTokenData = JSON.parse(data);

      if (tokenData.status !== 'pending') {
        return { success: false, error: 'Token already used' };
      }

      if (Date.now() > tokenData.expiresAt) {
        await redis.del(`${SCAN_TOKEN_PREFIX}${scanToken}`);
        return { success: false, error: 'Token expired' };
      }

      tokenData.status = 'scanned';

      const ttl = await redis.ttl(`${SCAN_TOKEN_PREFIX}${scanToken}`);
      await redis.setex(
        `${SCAN_TOKEN_PREFIX}${scanToken}`,
        ttl > 0 ? ttl : SCAN_TOKEN_TTL,
        JSON.stringify(tokenData)
      );

      logger.info({ scanToken }, 'Token marked as scanned');

      return { success: true, pcSocketId: tokenData.pcSocketId };
    } catch (error) {
      logger.error({ error, scanToken }, 'Failed to mark token as scanned');
      return { success: false, error: 'Failed to mark token as scanned' };
    }
  }

  /**
   * Confirm login with user info
   */
  async confirmLogin(
    scanToken: string,
    userName: string,
    department: string
  ): Promise<{
    success: boolean;
    data?: {
      sessionToken: string;
      userId: string;
      pcSocketId: string;
    };
    error?: string;
  }> {
    try {
      const data = await redis.get(`${SCAN_TOKEN_PREFIX}${scanToken}`);

      if (!data) {
        return { success: false, error: 'Token not found or expired' };
      }

      const tokenData: ScanTokenData = JSON.parse(data);

      if (tokenData.status !== 'scanned') {
        return { success: false, error: 'Invalid token status' };
      }

      if (Date.now() > tokenData.expiresAt) {
        await redis.del(`${SCAN_TOKEN_PREFIX}${scanToken}`);
        return { success: false, error: 'Token expired' };
      }

      // Generate user ID and session token
      const userId = `user_${Date.now()}_${uuidv4().slice(0, 8)}`;
      const sessionToken = jwt.sign(
        { userId, userName, department },
        config.jwtSecret || 'gala-secret-key',
        { expiresIn: '24h' }
      );

      // Store session
      await redis.setex(
        `${SESSION_TOKEN_PREFIX}${sessionToken}`,
        SESSION_TOKEN_TTL,
        JSON.stringify({ userId, userName, department })
      );

      // Update scan token status
      tokenData.status = 'confirmed';
      tokenData.userInfo = { userId, userName, department };

      await redis.setex(
        `${SCAN_TOKEN_PREFIX}${scanToken}`,
        60, // Keep for 1 minute for status sync
        JSON.stringify(tokenData)
      );

      logger.info({ scanToken, userId, userName }, 'Login confirmed');

      return {
        success: true,
        data: {
          sessionToken,
          userId,
          pcSocketId: tokenData.pcSocketId,
        },
      };
    } catch (error) {
      logger.error({ error, scanToken }, 'Failed to confirm login');
      return { success: false, error: 'Failed to confirm login' };
    }
  }

  /**
   * Get token data
   */
  async getTokenData(scanToken: string): Promise<ScanTokenData | null> {
    try {
      const data = await redis.get(`${SCAN_TOKEN_PREFIX}${scanToken}`);
      return data ? JSON.parse(data) : null;
    } catch (error) {
      logger.error({ error, scanToken }, 'Failed to get token data');
      return null;
    }
  }
}

export const scanLoginService = new ScanLoginService();
