import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index'
import { z } from 'zod'
import bcrypt from 'bcryptjs'
import { SignJWT } from 'jose';
import { generateSignedUrlFromS3Url } from '@/src/lib/s3-client'
import { ApiError } from '@/src/lib/api-error'
import { getEncodedJwtSecret } from '@/src/lib/env-exporter'
import { sendOtp, verifyOtpUtil, getOtpCreds } from '@/src/lib/otp-utils'
import {
  getUserAuthByEmail as getUserAuthByEmailInDb,
  getUserAuthByMobile as getUserAuthByMobileInDb,
  getUserAuthById as getUserAuthByIdInDb,
  getUserAuthCreds as getUserAuthCredsInDb,
  getUserAuthDetails as getUserAuthDetailsInDb,
  createUserAuthWithMobile as createUserAuthWithMobileInDb,
  upsertUserAuthPassword as upsertUserAuthPasswordInDb,
  deleteUserAuthAccount as deleteUserAuthAccountInDb,
  createUserWithProfile as createUserWithProfileInDb,
  updateUserProfile as updateUserProfileInDb,
  getUserDetailsByUserId as getUserDetailsByUserIdInDb,
} from '@/src/dbService'
import type {
  UserAuthResult,
  UserAuthResponse,
  UserOtpVerifyResponse,
  UserPasswordUpdateResponse,
  UserProfileResponse,
  UserDeleteAccountResponse,
} from '@packages/shared'

interface LoginRequest {
  identifier: string;
  password: string;
}

interface RegisterRequest {
  name: string;
  email: string;
  mobile: string;
  password: string;
  profileImageUrl?: string | null;
}

const generateToken = async (userId: number): Promise<string> => {
  return await new SignJWT({ userId })
    .setProtectedHeader({ alg: 'HS256' })
    .setExpirationTime('90d')
    .sign(getEncodedJwtSecret());
};

const setAuthCookie = (ctx: any, token: string) => {
  if (ctx.callerInterface === 'web' && ctx.c) {
    ctx.c.header(
      'Set-Cookie',
      `auth_token=${token}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=604800`
    )
  }
}

export const authRouter = router({
  login: publicProcedure
    .input(z.object({
      identifier: z.string().min(1, 'Email/mobile is required'),
      password: z.string().min(1, 'Password is required'),
    }))
    .mutation(async ({ input, ctx }): Promise<UserAuthResult> => {
      const { identifier, password }: LoginRequest = input;

      if (!identifier || !password) {
        throw new ApiError('Email/mobile and password are required', 400);
      }

      // Find user by email or mobile
      const user = await getUserAuthByEmailInDb(identifier.toLowerCase())
      let foundUser = user || null

      if (!foundUser) {
        // Try mobile if email didn't work
        const userByMobile = await getUserAuthByMobileInDb(identifier)
        foundUser = userByMobile || null
      }

      if (!foundUser) {
        throw new ApiError('Invalid credentials', 401);
      }

      // Get user credentials
      const userCredentials = await getUserAuthCredsInDb(foundUser.id)

      if (!userCredentials) {
        throw new ApiError('Account setup incomplete. Please contact support.', 401);
      }

      // Get user details for profile image
      const userDetail = await getUserAuthDetailsInDb(foundUser.id)

      // Generate signed URL for profile image if it exists
      const profileImageSignedUrl = userDetail?.profileImage
        ? await generateSignedUrlFromS3Url(userDetail.profileImage)
        : null;

      // Verify password
      const isPasswordValid = await bcrypt.compare(password, userCredentials.userPassword);
      if (!isPasswordValid) {
        throw new ApiError('Invalid credentials', 401);
      }

      const token = await generateToken(foundUser.id);
      setAuthCookie(ctx, token);

      const response: UserAuthResponse = {
        token,
        user: {
          id: foundUser.id,
          name: foundUser.name,
          email: foundUser.email,
          mobile: foundUser.mobile,
          createdAt: foundUser.createdAt.toISOString(),
          profileImage: profileImageSignedUrl,
          bio: userDetail?.bio || null,
          dateOfBirth: userDetail?.dateOfBirth
            ? new Date(userDetail.dateOfBirth as any).toISOString()
            : null,
          gender: userDetail?.gender || null,
          occupation: userDetail?.occupation || null,
        },
      };

      return {
        success: true,
        data: response,
      }
    }),

  register: publicProcedure
    .input(z.object({
      name: z.string().min(1, 'Name is required'),
      email: z.string().email('Invalid email format'),
      mobile: z.string().min(1, 'Mobile is required'),
      password: z.string().min(1, 'Password is required'),
      profileImageUrl: z.string().nullable().optional(),
    }))
    .mutation(async ({ input, ctx }): Promise<UserAuthResult> => {
      const { name, email, mobile, password, profileImageUrl }: RegisterRequest = input;

      if (!name || !email || !mobile || !password) {
        throw new ApiError('All fields are required', 400);
      }

      // Validate email format
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      if (!emailRegex.test(email)) {
        throw new ApiError('Invalid email format', 400);
      }

      // Validate mobile format (Indian mobile numbers)
      const cleanMobile = mobile.replace(/\D/g, '');
      if (cleanMobile.length !== 10 || !/^[6-9]/.test(cleanMobile)) {
        throw new ApiError('Invalid mobile number', 400);
      }

      // Check if email already exists
      const existingEmail = await getUserAuthByEmailInDb(email.toLowerCase())

      if (existingEmail) {
        throw new ApiError('Email already registered', 409);
      }

      // Check if mobile already exists
      const existingMobile = await getUserAuthByMobileInDb(cleanMobile)

      if (existingMobile) {
        throw new ApiError('Mobile number already registered', 409);
      }

      // Hash password
      const hashedPassword = await bcrypt.hash(password, 12);

      // Create user and credentials in a transaction
      const newUser = await createUserWithProfileInDb({
        name: name.trim(),
        email: email.toLowerCase().trim(),
        mobile: cleanMobile,
        hashedPassword,
        profileImage: profileImageUrl ?? null,
      })

      const token = await generateToken(newUser.id);
      setAuthCookie(ctx, token);

      const profileImageSignedUrl = profileImageUrl
        ? await generateSignedUrlFromS3Url(profileImageUrl)
        : null

      const response: UserAuthResponse = {
        token,
        user: {
          id: newUser.id,
          name: newUser.name,
          email: newUser.email,
          mobile: newUser.mobile,
          createdAt: newUser.createdAt.toISOString(),
          profileImage: profileImageSignedUrl,
        },
      };

      return {
        success: true,
        data: response,
      }
    }),

  sendOtp: publicProcedure
    .input(z.object({
      mobile: z.string(),
    }))
    .mutation(async ({ input }) => {
      
      return await sendOtp(input.mobile);
    }),

  verifyOtp: publicProcedure
    .input(z.object({
      mobile: z.string(),
      otp: z.string(),
    }))
    .mutation(async ({ input, ctx }): Promise<UserOtpVerifyResponse> => {
      const verificationId = getOtpCreds(input.mobile);
      if (!verificationId) {
        throw new ApiError("OTP not sent or expired", 400);
      }
      const isVerified = await verifyOtpUtil(input.mobile, input.otp, verificationId);

      if (!isVerified) {
        throw new ApiError("Invalid OTP", 400);
      }

      // Find user
       let user = await getUserAuthByMobileInDb(input.mobile)

        // If user doesn't exist, create one
         if (!user) {
           user = await createUserAuthWithMobileInDb(input.mobile)
         }

        // Generate JWT
        const token = await generateToken(user.id);
        setAuthCookie(ctx, token);

       return {
         success: true,
         token,
         user: {
           id: user.id,
           name: user.name,
           email: user.email,
           mobile: user.mobile,
           createdAt: user.createdAt.toISOString(),
           profileImage: null,
         },
       }
    }),

  updatePassword: protectedProcedure
    .input(z.object({
      password: z.string().min(6, 'Password must be at least 6 characters'),
    }))
    .mutation(async ({ input, ctx }): Promise<UserPasswordUpdateResponse> => {
      const userId = ctx.user.userId;
      if (!userId) {
        throw new ApiError('User not authenticated', 401);
      }

      const hashedPassword = await bcrypt.hash(input.password, 10);

      // Insert if not exists, then update if exists
      await upsertUserAuthPasswordInDb(userId, hashedPassword)

      /*
      // Old implementation - direct DB queries:
      try {
        await db.insert(userCreds).values({
          userId: userId,
          userPassword: hashedPassword,
        });
      } catch (error: any) {
        if (error.code === '23505') {
          await db.update(userCreds).set({
            userPassword: hashedPassword,
          }).where(eq(userCreds.userId, userId));
        } else {
          throw error;
        }
      }
      */

      return { success: true, message: 'Password updated successfully' }
    }),

  updateProfile: protectedProcedure
    .input(z.object({
      name: z.string().min(1).optional(),
      email: z.string().email('Invalid email format').optional(),
      mobile: z.string().min(1).optional(),
      password: z.string().min(6, 'Password must be at least 6 characters').optional(),
      bio: z.string().optional().nullable(),
      dateOfBirth: z.string().optional().nullable(),
      gender: z.string().optional().nullable(),
      occupation: z.string().optional().nullable(),
      profileImageUrl: z.string().optional().nullable(),
    }))
    .mutation(async ({ input, ctx }): Promise<UserAuthResult> => {
      const userId = ctx.user.userId;

      if (!userId) {
        throw new ApiError('User not authenticated', 401);
      }

      const { name, email, mobile, password, bio, dateOfBirth, gender, occupation, profileImageUrl } = input

      if (email) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(email)) {
          throw new ApiError('Invalid email format', 400);
        }
      }

      if (mobile) {
        const cleanMobile = mobile.replace(/\D/g, '');
        if (cleanMobile.length !== 10 || !/^[6-9]/.test(cleanMobile)) {
          throw new ApiError('Invalid mobile number', 400);
        }
      }

      if (email) {
        const existingEmail = await getUserAuthByEmailInDb(email.toLowerCase())
        if (existingEmail && existingEmail.id !== userId) {
          throw new ApiError('Email already registered', 409)
        }
      }

      if (mobile) {
        const cleanMobile = mobile.replace(/\D/g, '')
        const existingMobile = await getUserAuthByMobileInDb(cleanMobile)
        if (existingMobile && existingMobile.id !== userId) {
          throw new ApiError('Mobile number already registered', 409)
        }
      }

      let hashedPassword: string | undefined;
      if (password) {
        hashedPassword = await bcrypt.hash(password, 12)
      }

      const updatedUser = await updateUserProfileInDb(userId, {
        name: name?.trim(),
        email: email?.toLowerCase().trim(),
        mobile: mobile?.replace(/\D/g, ''),
        hashedPassword,
        profileImage: profileImageUrl ?? undefined,
        bio: bio ?? undefined,
        dateOfBirth: dateOfBirth ? new Date(dateOfBirth) : undefined,
        gender: gender ?? undefined,
        occupation: occupation ?? undefined,
      })

      const userDetail = await getUserDetailsByUserIdInDb(userId)
      const profileImageSignedUrl = userDetail?.profileImage
        ? await generateSignedUrlFromS3Url(userDetail.profileImage)
        : null

      const authHeader = ctx.req.header('authorization');
      const token = authHeader?.replace('Bearer ', '') || ''

      const response: UserAuthResponse = {
        token,
        user: {
          id: updatedUser.id,
          name: updatedUser.name,
          email: updatedUser.email,
          mobile: updatedUser.mobile,
          createdAt: updatedUser.createdAt?.toISOString?.() || new Date().toISOString(),
          profileImage: profileImageSignedUrl,
          bio: userDetail?.bio || null,
          dateOfBirth: userDetail?.dateOfBirth
            ? new Date(userDetail.dateOfBirth as any).toISOString()
            : null,
          gender: userDetail?.gender || null,
          occupation: userDetail?.occupation || null,
        },
      }

      return {
        success: true,
        data: response,
      }
    }),

  getProfile: protectedProcedure
    .query(async ({ ctx }): Promise<UserProfileResponse> => {
      const userId = ctx.user.userId;

      if (!userId) {
        throw new ApiError('User not authenticated', 401);
      }

      const user = await getUserAuthByIdInDb(userId)

      if (!user) {
        throw new ApiError('User not found', 404);
      }

      return {
        success: true,
        data: {
          id: user.id,
          name: user.name,
          email: user.email,
          mobile: user.mobile,
        },
      }
    }),

  deleteAccount: protectedProcedure
    .input(z.object({
      mobile: z.string().min(10, 'Mobile number is required'),
    }))
    .mutation(async ({ ctx, input }): Promise<UserDeleteAccountResponse> => {
      const userId = ctx.user.userId;
      const { mobile } = input;
      
      if (!userId) {
        throw new ApiError('User not authenticated', 401);
      }

      // Double-check: verify user exists and is the authenticated user
      const existingUser = await getUserAuthByIdInDb(userId)

      if (!existingUser) {
        throw new ApiError('User not found', 404);
      }

      // Additional verification: ensure we're not deleting someone else's data
      // The JWT token should already ensure this, but double-checking
      if (existingUser.id !== userId) {
        throw new ApiError('Unauthorized: Cannot delete another user\'s account', 403);
      }

      // Verify mobile number matches user's registered mobile
      const cleanInputMobile = mobile.replace(/\D/g, '');
      const cleanUserMobile = existingUser.mobile?.replace(/\D/g, '');

      if (cleanInputMobile !== cleanUserMobile) {
        throw new ApiError('Mobile number does not match your registered number', 400);
      }

      // Use transaction for atomic deletion
      await deleteUserAuthAccountInDb(userId)

      /*
      // Old implementation - direct DB queries:
      await db.transaction(async (tx) => {
        await tx.delete(notifCreds).where(eq(notifCreds.userId, userId));
        await tx.delete(couponApplicableUsers).where(eq(couponApplicableUsers.userId, userId));
        await tx.delete(couponUsage).where(eq(couponUsage.userId, userId));
        await tx.delete(complaints).where(eq(complaints.userId, userId));
        await tx.delete(cartItems).where(eq(cartItems.userId, userId));
        await tx.delete(notifications).where(eq(notifications.userId, userId));
        await tx.delete(productReviews).where(eq(productReviews.userId, userId));
        await tx.update(reservedCoupons)
          .set({ redeemedBy: null })
          .where(eq(reservedCoupons.redeemedBy, userId));

        const userOrders = await tx
          .select({ id: orders.id })
          .from(orders)
          .where(eq(orders.userId, userId));

        for (const order of userOrders) {
          await tx.delete(orderItems).where(eq(orderItems.orderId, order.id));
          await tx.delete(orderStatus).where(eq(orderStatus.orderId, order.id));
          await tx.delete(payments).where(eq(payments.orderId, order.id));
          await tx.delete(refunds).where(eq(refunds.orderId, order.id));
          await tx.delete(couponUsage).where(eq(couponUsage.orderId, order.id));
          await tx.delete(complaints).where(eq(complaints.orderId, order.id));
        }

        await tx.delete(orders).where(eq(orders.userId, userId));
        await tx.delete(addresses).where(eq(addresses.userId, userId));
        await tx.delete(userDetails).where(eq(userDetails.userId, userId));
        await tx.delete(userCreds).where(eq(userCreds.userId, userId));
        await tx.delete(users).where(eq(users.id, userId));
      });
      */

      return { success: true, message: 'Account deleted successfully' }
    }),
});
