"use server";

import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/authOptions";
import type { Session } from "next-auth";
import { prisma } from "@/lib/prisma";
import type { Device, Rule } from "./DashboardDataProvider";
import punycode from "punycode";
const MAX_DEVICES = 5;
const validNameRegex = /^[\w\s\-']{1,255}$/;

// !!! user validation helpers !!! ----------------------------------------
// ----------------------------------------------------------------------
/**
 * Validates that user exists in database (for critical operations)
 * Use this when you need to be 100% sure user still exists
 */
export async function validateUserExists(userId: string) {
  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: {
      id: true,
      email: true,
      name: true,
      pinCode: true,
      timezoneOffset: true,
    },
  });

  if (!user) {
    throw new Error("User no longer exists");
  }

  return user;
}

/**
 * Gets fresh user data from database (bypasses JWT cache)
 * Use sparingly - only for critical operations that need fresh data
 */
export async function getFreshUserData(userId: string) {
  return await validateUserExists(userId);
}

// !!! session actions !!! -----------------------------------------------
// ----------------------------------------------------------------------
export async function updateSession(): Promise<Session | null> {
  const session = await getServerSession(authOptions);
  return session;
}

/**
 * Enhanced session validation with graceful handling
 * Returns session if valid, or indicates redirect needed
 */
export async function validateAndRefreshSession(): Promise<
  | { success: true; session: Session; userId: string }
  | { success: false; shouldRedirect: true }
> {
  // First attempt - get current session
  let session = await getServerSession(authOptions);

  // If no session or user, try refreshing once
  if (!session?.user?.id) {
    console.log("[Auth] No session found, attempting refresh...");
    session = await updateSession();
  }

  // After refresh, check again
  if (!session?.user?.id) {
    console.log("[Auth] No valid session after refresh, redirect needed");
    return { success: false, shouldRedirect: true };
  }

  console.log(`[Auth] Valid session found for user: ${session.user.email}`);
  return {
    success: true,
    session,
    userId: session.user.id,
  };
}

// !!! logs actions !!! -----------------------------------------------
// ----------------------------------------------------------------------
export async function logError(message: string) {
  await prisma.errorLog.create({
    data: {
      text: message,
    },
  });
}
export async function logDeviceMessage(deviceId: string, message: string) {
  await prisma.deviceLog.create({
    data: {
      deviceId,
      message,
    },
  });
}

export async function clearDeviceLogs(deviceId: string): Promise<
  | {
      success: true;
      session: Session;
    }
  | {
      success: false;
      error: string;
      shouldRedirect?: boolean;
    }
> {
  const authResult = await validateAndRefreshSession();

  if (!authResult.success) {
    return {
      success: false,
      error: "Authentication required",
      shouldRedirect: true,
    };
  }

  const { session, userId } = authResult;

  // Verify the device belongs to the user
  const device = await prisma.device.findFirst({
    where: { deviceId, userId },
    select: { id: true },
  });

  if (!device) {
    return { success: false, error: "Device not found or access denied" };
  }

  // Delete all logs for this device
  await prisma.deviceLog.deleteMany({
    where: { deviceId },
  });

  return { success: true, session };
}

// !!! device actions !!! -----------------------------------------------
// ----------------------------------------------------------------------

/**
 * NEW: Load all dashboard data in a single optimized query
 * Returns devices with embedded rules, instructions, and logs
 */
export async function loadDashboardData(): Promise<{
  devices: Device[];
  session: Session | null;
}> {
  const authResult = await validateAndRefreshSession();

  if (!authResult.success) {
    console.log(
      "[loadDashboardData] No valid session, returning empty devices"
    );
    return { devices: [], session: null };
  }

  const { session, userId } = authResult;

  // Single query with all necessary joins
  const rawDevices = await prisma.device.findMany({
    where: { userId },
    select: {
      id: true,
      deviceId: true,
      deviceName: true,
      assigningCode: true,
      restrictedCategoryIds: true,
      syncStatus: true,
      createdAt: true,
      lastOnlineAt: true,
      rules: {
        select: {
          id: true,
          deviceId: true,
          type: true,
          value: true,
          schedule: true,
        },
      },
      instructions: {
        orderBy: { createdAt: "desc" },
        take: 1,
        select: {
          resetConnection: true,
          sound: true,
          createdAt: true,
          lastOnline: true,
        },
      },
      logs: {
        orderBy: { createdAt: "desc" },
        take: 50, // Last 50 log entries
        select: {
          message: true,
        },
      },
    },
  });

  // Transform to new Device structure
  const devices: Device[] = rawDevices.map((d) => {
    const latestInstruction = d.instructions?.[0];

    return {
      id: d.id,
      deviceId: d.deviceId,
      deviceName: d.deviceName,
      assigningCode: d.assigningCode,
      restrictedCategoryIds: d.restrictedCategoryIds ?? "",
      rules: d.rules.map(
        (rule): Rule => ({
          id: rule.id,
          deviceId: rule.deviceId,
          type: rule.type as "custom_url" | "preset",
          value: rule.value,
          schedule: rule.schedule,
        })
      ),
      syncStatus: d.syncStatus ?? true,
      resetConnection: latestInstruction?.resetConnection ?? false,
      sound: latestInstruction?.sound ?? 50,
      createdAt: latestInstruction?.createdAt ?? d.createdAt,
      lastOnline: latestInstruction?.lastOnline || d.lastOnlineAt || null,
      deviceLog: d.logs.map((log) => log.message),
    };
  });

  return {
    devices,
    session,
  };
}

// LEGACY: Keep for backward compatibility - will be removed in future updates
export async function loadDevices(): Promise<{
  devices: Device[];
  deviceInstructionsById: Record<
    string,
    {
      resetConnection: boolean;
      sound: number;
      createdAt: Date;
      lastOnline: Date | null;
    }
  >;
  session: Session | null;
}> {
  const authResult = await validateAndRefreshSession();

  if (!authResult.success) {
    // Return empty data instead of throwing - let UI handle redirect
    console.log("[loadDevices] No valid session, returning empty devices");
    return { devices: [], deviceInstructionsById: {}, session: null };
  }

  const { session, userId } = authResult;

  const rawDevices = await prisma.device.findMany({
    where: { userId },
    select: {
      id: true,
      deviceId: true,
      deviceName: true,
      assigningCode: true,
      restrictedCategoryIds: true,
      syncStatus: true,
      userId: true,
      metadata: true,
      createdAt: true,
      updatedAt: true,
      lastOnlineAt: true,
      instructions: {
        orderBy: { createdAt: "desc" },
        take: 1,
        select: {
          resetConnection: true,
          sound: true,
          createdAt: true,
          lastOnline: true,
        },
      },
    },
  });

  const devices: Device[] = [];
  const deviceInstructionsById: Record<
    string,
    {
      resetConnection: boolean;
      sound: number;
      createdAt: Date;
      lastOnline: Date | null;
    }
  > = {};

  for (const d of rawDevices) {
    const latest = d.instructions?.[0];

    devices.push({
      id: d.id,
      deviceId: d.deviceId,
      deviceName: d.deviceName,
      assigningCode: d.assigningCode,
      syncStatus: d.syncStatus ?? true,
      restrictedCategoryIds: d.restrictedCategoryIds ?? "",
      rules: [], // Legacy: rules loaded separately
      resetConnection: latest?.resetConnection ?? false,
      sound: latest?.sound ?? 50,
      createdAt: latest?.createdAt ?? d.createdAt,
      lastOnline: latest?.lastOnline || d.lastOnlineAt || null,
      deviceLog: [], // Legacy: logs not included
    });

    deviceInstructionsById[d.deviceId] = {
      resetConnection: latest?.resetConnection ?? false,
      sound: latest?.sound ?? 0,
      createdAt: latest?.createdAt,
      lastOnline: latest?.lastOnline || null,
    };
  }

  return {
    devices,
    deviceInstructionsById,
    session,
  };
}

// create device
export async function createDeviceServerAction(
  name: string
): Promise<
  | { success: true; device: Device; session: Session }
  | { success: false; error: string; shouldRedirect?: boolean }
> {
  const authResult = await validateAndRefreshSession();

  if (!authResult.success) {
    console.log("[createDevice] No valid session");
    return {
      success: false,
      error: "Authentication required",
      shouldRedirect: true,
    };
  }

  const { session, userId } = authResult;
  const generateAssigningCode = () => {
    return Math.floor(100000000000 + Math.random() * 900000000000).toString();
  };
  const trimmedName = name.trim();
  if (!trimmedName || trimmedName.length > 25) {
    return {
      success: false,
      error: "Device name must be between 1 and 25 characters",
    };
  }

  if (!validNameRegex.test(trimmedName)) {
    return { success: false, error: "Device name contains invalid characters" };
  }

  const count = await prisma.device.count({
    where: { userId },
  });
  if (count >= MAX_DEVICES) {
    return { success: false, error: "Device limit reached (max 5 per user)." };
  }

  for (let i = 0; i < 3; i++) {
    try {
      const newDevice = await prisma.$transaction(async (tx) => {
        const created = await tx.device.create({
          data: {
            userId: session.user.id,
            deviceId: `d${Date.now()}`,
            deviceName: trimmedName,
            assigningCode: generateAssigningCode(),
          },
        });

        const pin = session.user.pinCode || "123456";
        await tx.deviceInstructions.create({
          data: {
            deviceId: created.deviceId,
            deviceUid: created.assigningCode ?? "",
            syncStatus: true,
            version: 1,
            pinCode: pin,
            info: null,
            instructions: "",
            pinStatus: 0,
            updateType: 2,
          },
        });

        return created;
      });

      for (let attempt = 1; attempt <= 3; attempt++) {
        try {
          const res = await fetch("https://guard.alexweb.app/checker/ping", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              command: "revalidate",
              info: `Device "${trimmedName}" created for user: ${session.user.email}`,
            }),
          });

          if (!res.ok) throw new Error(`HTTP ${res.status}`);
          console.log(
            `[Checker Notify] Revalidate success on attempt ${attempt}`
          );
          break; // ✅ Exit loop on success
        } catch (err) {
          console.error(`[Checker Notify] Attempt ${attempt} failed:`, err);
          if (attempt === 3) {
            console.error("[Checker Notify] All 3 attempts failed.");
          }
        }
      }

      return {
        success: true,
        device: {
          ...newDevice,
          syncStatus: true,
          restrictedCategoryIds: "",
          rules: [],
          resetConnection: false,
          sound: 50,
          lastOnline: null,
          deviceLog: [],
        },
        session,
      };
    } catch (e) {
      console.error("Device creation failed, retrying...", e);
    }
  }

  return { success: false, error: "Could not create device. Try again." };
}

// delete device
export async function deleteDevice(deviceId: string): Promise<{
  devices: Device[];
  session: Session;
}> {
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) throw new Error("Unauthorized");

  const userId = session.user.id;

  for (let attempt = 0; attempt < 2; attempt++) {
    try {
      const result = await prisma.$transaction(async (tx) => {
        const deleteResult = await tx.device.deleteMany({
          where: {
            deviceId,
            userId,
          },
        });

        if (deleteResult.count === 0) {
          throw new Error("Device not found or access denied");
        }

        const updatedDevices = await tx.device.findMany({
          where: { userId },
          select: {
            id: true,
            deviceId: true,
            deviceName: true,
            assigningCode: true,
            restrictedCategoryIds: true,
            syncStatus: true,
            userId: true,
            metadata: true,
            createdAt: true,
            updatedAt: true,
            lastOnlineAt: true,
          },
        });

        return {
          devices: updatedDevices.map((d) => ({
            id: d.id,
            deviceId: d.deviceId,
            deviceName: d.deviceName,
            assigningCode: d.assigningCode,
            restrictedCategoryIds: d.restrictedCategoryIds ?? "",
            rules: [],
            syncStatus: d.syncStatus ?? true,
            resetConnection: false,
            sound: 50,
            createdAt: d.createdAt,
            lastOnline: d.lastOnlineAt,
            deviceLog: [],
          })),
          session,
        };
      });

      return result;
    } catch (err) {
      console.error(`Attempt ${attempt + 1} to delete device failed:`, err);
      if (attempt === 1) throw new Error("Failed to delete device after retry");
    }
  }

  // fallback, should not hit
  throw new Error("Unexpected error during deletion");
}

// rename device
export async function renameDeviceName(deviceId: string, deviceName: string) {
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) throw new Error("Unauthorized");

  const trimmed = deviceName.trim();
  if (!trimmed || trimmed.length > 25) {
    throw new Error("Device name must be between 1 and 25 characters");
  }

  const validNameRegex = /^[\w\s\-']{1,255}$/;
  if (!validNameRegex.test(trimmed)) {
    throw new Error("Device name contains invalid characters");
  }

  const result = await prisma.device.updateMany({
    where: {
      deviceId,
      userId: session.user.id,
    },
    data: { deviceName: trimmed },
  });

  if (result.count === 0) throw new Error("Device not found or unauthorized");

  return {
    updatedName: trimmed,
    session,
  };
}

// switch status
export async function switchStatus(
  deviceId: string,
  syncStatus: boolean
): Promise<{
  syncStatus: boolean;
  session: Session;
}> {
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) throw new Error("Unauthorized access");

  const userId = session.user.id;
  const validNameRegex = /^d\d{13}$/;
  if (!validNameRegex.test(deviceId) || typeof syncStatus !== "boolean") {
    throw new Error("Incorrect data received");
  }

  for (let attempt = 0; attempt < 2; attempt++) {
    try {
      await prisma.$transaction(async (tx) => {
        const result = await tx.device.updateMany({
          where: { deviceId, userId },
          data: { syncStatus },
        });

        if (result.count === 0) {
          throw new Error("Device not found or access denied");
        }

        await tx.deviceInstructions.updateMany({
          where: { deviceId },
          data: { syncStatus, createdAt: new Date() },
        });
      });

      return { syncStatus, session };
    } catch (err) {
      console.error(`Attempt ${attempt + 1} to switch status failed:`, err);
      if (attempt === 1) throw new Error("Failed to switch status after retry");
    }
  }

  throw new Error("Unexpected error during status switch");
}
// switch connection:
export async function switchResetConnection(
  deviceId: string,
  resetConnection: boolean
): Promise<{
  resetConnection: boolean;
  session: Session;
}> {
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) throw new Error("Unauthorized access");

  const userId = session.user.id;
  const validNameRegex = /^d\d{13}$/;

  if (!validNameRegex.test(deviceId) || typeof resetConnection !== "boolean") {
    throw new Error("Incorrect data received");
  }

  try {
    const updated = await prisma.deviceInstructions.updateMany({
      where: {
        deviceId,
        device: {
          userId,
        },
      },
      data: {
        resetConnection,
        createdAt: new Date(),
      },
    });

    if (updated.count === 0) {
      throw new Error("Device instructions not found or access denied");
    }

    return { resetConnection, session };
  } catch (err) {
    console.error("Failed to update resetConnection:", err);
    throw new Error("Unexpected error during resetConnection update");
  }
}

// rules actions
export async function loadRules(deviceId?: string) {
  const authResult = await validateAndRefreshSession();

  if (!authResult.success) {
    // Return empty array instead of throwing - let UI handle redirect
    console.log("[loadRules] No valid session, returning empty rules");
    return [];
  }

  const { userId } = authResult;

  const rules = await prisma.rule.findMany({
    where: {
      device: { userId },
      ...(deviceId && { deviceId }),
    },
    select: {
      id: true,
      deviceId: true,
      type: true,
      value: true,
      schedule: true,
      createdAt: true,
      updatedAt: true,
    },
  });

  return rules;
}
// create rule

export async function createRuleServerAction(input: {
  deviceId: string;
  type: "custom_url" | "preset";
  value: string;
  schedule: string;
}): Promise<Rule> {
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) {
    throw new Error("Unauthorized");
  }

  const { deviceId, type, value, schedule } = input;

  const allowedModes = ["1", "2", "3", "4"];
  if (
    !deviceId ||
    !value ||
    typeof schedule !== "string" ||
    (schedule.length !== 56 && schedule.length !== 57) ||
    !allowedModes.includes(schedule.charAt(0))
  ) {
    throw new Error("Missing or invalid fields");
  }

  const blocks = schedule.slice(1).match(/.{8}/g);
  const isValidBlock = (block: string) =>
    block === "--------" || /^[0-9]{8}$/.test(block);
  if (!blocks || blocks.length !== 7 || !blocks.every(isValidBlock)) {
    throw new Error("Invalid schedule format");
  }

  let normalizedHost: string;
  try {
    const raw = value.trim();
    const withProtocol = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
    const parsed = new URL(withProtocol);
    normalizedHost = punycode.toASCII(parsed.hostname);
    if (!normalizedHost || normalizedHost.includes(" ")) {
      throw new Error("Invalid domain");
    }
  } catch {
    throw new Error("Invalid URL");
  }

  let finalType = type;
  let finalValue = normalizedHost;

  if (type !== "preset") {
    const preset = await prisma.preset.findFirst({
      where: {
        OR: [
          { url: normalizedHost },
          { domains: { contains: normalizedHost } },
        ],
      },
    });
    if (preset) {
      finalType = "preset";
      finalValue = preset.url;
    }
  }

  const rule = await prisma.rule.create({
    data: {
      deviceId,
      type: finalType,
      value: finalValue,
      schedule,
    },
  });

  return {
    ...rule,
    type: rule.type as "preset" | "custom_url",
  };
}

// INSTRUCTIONS
//-----------------------
export async function sendDeviceInstructionsServerAction(
  deviceId: string,
  assignCode: string
): Promise<
  { success: true; session: Session } | { success: false; error: string }
> {
  const session = await getServerSession(authOptions);
  try {
    if (!session?.user?.id) throw new Error("Unauthorized");

    const validIdRegex = /^[a-zA-Z0-9.]+$/;
    if (!validIdRegex.test(deviceId) || !validIdRegex.test(assignCode)) {
      throw new Error("Invalid input");
    }

    const userId = session.user.id;

    const device = await prisma.device.findFirst({
      where: { deviceId, userId, assigningCode: assignCode },
    });
    if (!device) throw new Error("Device not found or not owned");

    const [rules, existingInstructions] = await Promise.all([
      prisma.rule.findMany({ where: { deviceId } }),
      prisma.deviceInstructions.findFirst({
        where: { deviceUid: assignCode },
      }),
    ]);

    if (!existingInstructions) {
      throw new Error("Instruction record not found for device");
    }

    const instructionsObj = {
      restrictedCategoryIds: device.restrictedCategoryIds,
      rules,
    };
    const previousInstructions = existingInstructions.instructions;
    const currentInstructions = JSON.stringify(instructionsObj);

    if (previousInstructions === currentInstructions) {
      throw new Error("No changes in instructions were made.");
    }

    if (existingInstructions.updateType > 5) {
      const now = new Date();
      const createdAt = new Date(existingInstructions.createdAt);
      const diffMs = now.getTime() - createdAt.getTime();
      if (diffMs < 60 * 60 * 1000) {
        await logError(
          `Instruction rate limit: deviceId=${deviceId}, time=${now.toISOString()}`
        );
        throw new Error("To many instructions sent. Continue in an hour.");
      }
    }

    const now = new Date();
    const createdAt = new Date(existingInstructions.createdAt);
    const isRecent = now.getTime() - createdAt.getTime() < 5 * 60 * 1000;
    const newUpdateType = isRecent ? existingInstructions.updateType + 1 : 1;

    await prisma.deviceInstructions.update({
      where: { deviceUid: assignCode },
      data: {
        updateType: newUpdateType,
        updateNeeded: true,
        instructions: JSON.stringify(instructionsObj),
        createdAt: now,
        version: { increment: 1 },
      },
    });

    return { success: true, session };
  } catch (error) {
    const errorMessage =
      error instanceof Error
        ? error.message
        : "Unknown error while sending instructions";
    console.error("Failed to send instructions:", errorMessage);
    return { success: false, error: errorMessage };
  }
}
