import { z } from "zod";
import { DEVICETYPE, ROLE, SOCIALS } from "../../constants/enums";
import validator from 'validator';
import dns from "dns";
import { promisify } from "util";
import { Types } from "mongoose";
import { OTP_SEND_REASON_ZOD, OTP_SEND_REASON } from "../../constants/otp";

const resolveMx = promisify(dns.resolveMx);

/** Reusable device object: `{ device_token, device_type }` */
export const deviceSchema = z.object({
  device_token: z.string().min(1, "Device token is required"),
  device_type: z
    .enum([...(Object.values(DEVICETYPE) as [string, ...string[]])])
    .optional()
    .default(DEVICETYPE.OTHER),
});

export type DeviceInput = z.infer<typeof deviceSchema>;

export const signupSchema = z.object({
  email: z.string()
    .email("Invalid email format")
    .max(255)
    .refine(async (email) => {
      if (!validator.isEmail(email)) {
        return false;
      }
      const domain = email.split('@')[1];
      try {
        const mxRecords = await resolveMx(domain);
        return mxRecords && mxRecords.length > 0;
      } catch (error) {
        console.error("DNS MX record lookup failed:", error);
        return false;
      }
    }, "Email domain does not exist or this email does not exist"),
  password: z
    .string()
    .min(8, "Password must be at least 8 characters long")
    .max(100),
  device: deviceSchema.optional(),
});

export const loginSchema = z.object({
  email: z.string().email("Invalid email format").max(255),
  password: z
    .string()
    .min(8, "Password must be at least 8 characters long")
    .max(100),
  device: deviceSchema.optional(),
});

/** POST /auth/send-otp — send code only */
export const otpSendSchema = z
  .object({
    email: z.string().email("Invalid email format").max(255).optional(),
    userId: z
      .string()
      .min(1)
      .refine((id) => Types.ObjectId.isValid(id), "Invalid userId")
      .optional(),
    reason: z.enum(OTP_SEND_REASON_ZOD).optional().default(OTP_SEND_REASON.REGISTRATION),
  })
  .superRefine((data, ctx) => {
    if (!data.email && !data.userId) {
      ctx.addIssue({
        path: ["email"],
        code: z.ZodIssueCode.custom,
        message: "email or userId is required",
      });
    }
  });

/** POST /auth/verify-otp — confirm code (otp + userId only) */
export const otpVerifySchema = z.object({
  userId: z
    .string()
    .min(1, "userId is required")
    .refine((id) => Types.ObjectId.isValid(id), "Invalid userId"),
  otp: z.string().min(4).max(10).trim(),
});

export const forgotPasswordSchema = z.object({
  email: z.string().email("Invalid email format").max(255),
});

export const createProfileSchema = z
  .object({
    name: z.string().max(255),
    gender: z.string(),
    age: z.number().optional(),
  })
  .refine(
    (data) =>
      data.gender !== "female" ||
      (data.gender === "female" && data.age !== undefined),
    {
      message: "Age is required unless gender is female",
      path: ["age"],
    }
  );

const profileLocationCoordinates = z.tuple([
  z.coerce
    .number()
    .min(-180, "Longitude must be between -180 and 180")
    .max(180, "Longitude must be between -180 and 180"),
  z.coerce
    .number()
    .min(-90, "Latitude must be between -90 and 90")
    .max(90, "Latitude must be between -90 and 90"),
]);

/** GeoJSON Point + human-readable address/label for profile (all fields optional when object is sent). */
export const profileLocationSchema = z
  .object({
    type: z.literal("Point").default("Point"),
    coordinates: profileLocationCoordinates.optional(),
    address: z.string().max(500).trim().optional(),
    label: z.string().max(100).trim().optional(),
  })
  .refine(
    (loc) => loc.coordinates !== undefined,
    {
      message: "coordinates are required when location is provided",
      path: ["coordinates"],
    },
  );

export const upsertProfileSchema = z.object({
  fullName: z.string().optional(),
  dob: z.string().optional(),
  address: z.string().optional(),
  phoneNumber: z.string().optional(),
  device: deviceSchema.optional(),
  location: profileLocationSchema.optional(),
  type: z.string().optional(),
});

export const socialLoginSchema = z.object({
  role: z.enum([...(Object.values(ROLE) as [string, ...string[]])]),
  accessToken: z.string().min(1, "Access token is required"),
  provider: z.enum([...(Object.values(SOCIALS) as [string, ...string[]])]),
  device: deviceSchema,
});

export const autoLoginSchema = z.object({
  device: deviceSchema.optional(),
});

export const logoutSchema = z.object({
  device: deviceSchema,
});

/** POST /api/v1/user/changePassword — mobile reset (Bearer JWT after verify-otp). */
export const changePasswordUserSchema = z
  .object({
    password: z
      .string()
      .min(8, "New password must be at least 8 characters")
      .max(100),
    confirm_password: z
      .string()
      .min(8, "Confirm password must be at least 8 characters")
      .max(100),
  })
  .refine((data) => data.password === data.confirm_password, {
    message: "Password and confirm password do not match",
    path: ["confirm_password"],
  });

/** POST /api/v1/auth/change-password — logged-in user (Bearer JWT). */
export const changePasswordSchema = z.object({
  oldPassword: z
    .string()
    .min(8, "Old password must be at least 8 characters")
    .max(100),
  newPassword: z
    .string()
    .min(8, "New password must be at least 8 characters")
    .max(100),
});
