/**
 * Session Manager Service
 * Handles user session lifecycle for account security management
 */

import { prisma } from '../db';
import { createId } from '@paralleldrive/cuid2';
import { getLocationFromIP } from '../geolocation';

const MAX_SESSIONS_PER_USER = 10;
const SESSION_EXPIRY_DAYS = 7;

interface DeviceInfo {
  deviceName: string;
  deviceType: 'desktop' | 'mobile' | 'tablet';
  userAgent: string;
}

/**
 * Parse User-Agent string to extract device information
 */
export function parseUserAgent(userAgent: string): Omit<DeviceInfo, 'userAgent'> {
  const ua = userAgent.toLowerCase();

  // Determine device type
  let deviceType: 'desktop' | 'mobile' | 'tablet' = 'desktop';
  if (/mobile|android|iphone|ipod|blackberry|iemobile|opera mini/i.test(ua)) {
    deviceType = 'mobile';
  } else if (/tablet|ipad|playbook|silk/i.test(ua)) {
    deviceType = 'tablet';
  }

  // Determine browser
  let browser = 'Unknown Browser';
  if (ua.includes('edg/')) browser = 'Edge';
  else if (ua.includes('chrome/') && !ua.includes('edg/')) browser = 'Chrome';
  else if (ua.includes('firefox/')) browser = 'Firefox';
  else if (ua.includes('safari/') && !ua.includes('chrome/')) browser = 'Safari';
  else if (ua.includes('opr/') || ua.includes('opera/')) browser = 'Opera';

  // Determine OS
  let os = 'Unknown OS';
  if (ua.includes('windows')) os = 'Windows';
  else if (ua.includes('mac os x')) os = 'macOS';
  else if (ua.includes('linux')) os = 'Linux';
  else if (ua.includes('android')) os = 'Android';
  else if (ua.includes('iphone') || ua.includes('ipad')) os = 'iOS';

  const deviceName = `${browser} on ${os}`;

  return {
    deviceName,
    deviceType,
  };
}

/**
 * Create a new session for user
 */
export async function createSession(
  userId: string,
  token: string,
  ipAddress: string,
  userAgent: string,
  loginHistoryId?: string // New parameter
): Promise<void> {
  try {
    // Parse device info
    const deviceInfo = parseUserAgent(userAgent);

    // Get location from IP
    const locationData = await getLocationFromIP(ipAddress);
    const location = locationData?.city && locationData?.country
      ? `${locationData.city}, ${locationData.country}`
      : null;

    // Clean up expired sessions first
    await cleanupExpiredSessions();

    // Check session limit
    const existingSessions = await prisma.user_sessions.count({
      where: { userId },
    });

    if (existingSessions >= MAX_SESSIONS_PER_USER) {
      // Delete oldest session
      const oldestSession = await prisma.user_sessions.findFirst({
        where: { userId },
        orderBy: { lastActive: 'asc' },
      });

      if (oldestSession) {
        // If oldest session is deleted, update its login_history record with logout time
        if (oldestSession.loginHistoryId) {
          await prisma.login_history.update({
            where: { id: oldestSession.loginHistoryId },
            data: { logoutAt: new Date() },
          });
        }
        await prisma.user_sessions.delete({
          where: { id: oldestSession.id },
        });
      }
    }

    // Create new session
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + SESSION_EXPIRY_DAYS);
    const sessionId = createId();

    // Save to Prisma first (source of truth)
    await prisma.user_sessions.create({
      data: {
        id: sessionId,
        userId,
        token,
        deviceName: deviceInfo.deviceName,
        deviceType: deviceInfo.deviceType,
        ipAddress,
        location,
        country: locationData?.country || null,
        city: locationData?.city || null,
        userAgent,
        expiresAt,
        loginHistoryId: loginHistoryId || null, // Store loginHistoryId
      },
    });

  } catch (error) {
    console.error('Failed to create session:', error);
    // Don't throw - session creation failure shouldn't block login
  }
}

/**
 * Validate if a session is still active
 */
export async function validateSession(token: string): Promise<boolean> {
  try {
    const session = await prisma.user_sessions.findUnique({
      where: { token },
    });

    if (!session) return false;

    // Check if expired
    if (new Date() > session.expiresAt) {
      await prisma.user_sessions.delete({
        where: { id: session.id },
      });
      return false;
    }

    // Update last active
    await prisma.user_sessions.update({
      where: { id: session.id },
      data: { lastActive: new Date() },
    });

    return true;
  } catch (error) {
    console.error('Failed to validate session:', error);
    return false;
  }
}

/**
 * Revoke a specific session
 */
export async function revokeSession(sessionId: string, userId: string): Promise<boolean> {
  try {
    const session = await prisma.user_sessions.findFirst({
      where: {
        id: sessionId,
        userId, // Ensure user owns this session
      },
    });

    if (!session) return false;

    // Update logoutAt in login_history if a linked record exists
    if (session.loginHistoryId) {
      await prisma.login_history.update({
        where: { id: session.loginHistoryId },
        data: { logoutAt: new Date() },
      });
    }

    await prisma.user_sessions.delete({
      where: { id: sessionId },
    });

    return true;
  } catch (error) {
    console.error('Failed to revoke session:', error);
    return false;
  }
}

/**
 * Revoke all sessions except the current one
 */
export async function revokeAllOtherSessions(userId: string, currentToken: string): Promise<number> {
  try {
    // First, find all sessions to be revoked (to update their login_history)
    const sessionsToRevoke = await prisma.user_sessions.findMany({
      where: {
        userId,
        token: {
          not: currentToken,
        },
      },
      select: {
        id: true,
        loginHistoryId: true,
      },
    });

    // Update logoutAt for all linked login_history records
    const loginHistoryIds = sessionsToRevoke
      .map(s => s.loginHistoryId)
      .filter((id): id is string => id !== null);

    if (loginHistoryIds.length > 0) {
      await prisma.login_history.updateMany({
        where: {
          id: { in: loginHistoryIds },
        },
        data: {
          logoutAt: new Date(),
        },
      });
    }

    // Now delete the sessions
    const result = await prisma.user_sessions.deleteMany({
      where: {
        userId,
        token: {
          not: currentToken,
        },
      },
    });

    return result.count;
  } catch (error) {
    console.error('Failed to revoke other sessions:', error);
    return 0;
  }
}

/**
 * Get all active sessions for a user
 */
export async function getUserSessions(userId: string) {
  try {
    const sessions = await prisma.user_sessions.findMany({
      where: { userId },
      orderBy: { lastActive: 'desc' },
    });

    return sessions;
  } catch (error) {
    console.error('Failed to get user sessions:', error);
    return [];
  }
}

/**
 * Cleanup expired sessions (cron job or manual call)
 */
export async function cleanupExpiredSessions(): Promise<number> {
  try {
    const expiredSessions = await prisma.user_sessions.findMany({
      where: {
        expiresAt: {
          lt: new Date(),
        },
      },
    });

    for (const session of expiredSessions) {
      if (session.loginHistoryId) {
        await prisma.login_history.update({
          where: { id: session.loginHistoryId },
          data: { logoutAt: new Date() },
        });
      }
    }

    const result = await prisma.user_sessions.deleteMany({
      where: {
        expiresAt: {
          lt: new Date(),
        },
      },
    });

    return result.count;
  } catch (error) {
    console.error('Failed to cleanup expired sessions:', error);
    return 0;
  }
}

/**
 * Mask IP address for privacy (192.168.***.*** )
 */
export function maskIPAddress(ip: string): string {
  const parts = ip.split('.');
  if (parts.length === 4) {
    return `${parts[0]}.${parts[1]}.***. ***`;
  }
  return ip;
}
