import { Router, Response } from 'express';
import bcrypt from 'bcryptjs';
import prisma from '../lib/prisma';
import { generateTokens, verifyRefreshToken } from '../lib/jwt';
import { asyncHandler, sendSuccess, AppError } from '../lib/utils';
import { authenticate, AuthRequest } from '../middleware/auth';
import { validate } from '../lib/utils';
import {
  registerSchema,
  loginSchema,
  googleAuthSchema,
  refreshSchema,
  sendEmailOTPSchema,
  resendEmailOTPSchema,
  sendPhoneOTPSchema,
  verifyPhoneOTPSchema,
} from '../validators/schemas';
import { createOTP, verifyOTP } from '../services/otp.service';
import { verifyGoogleIdToken } from '../services/googleAuth.service';
import { normalizePhilippinePhone, philippinePhoneVariants } from '../lib/phone';
import { syncOwnerVerification } from '../services/verificationSync.service';

const router = Router();

const WORKER_REGISTER_OTP_PURPOSE = 'worker_register';
const CUSTOMER_REGISTER_OTP_PURPOSE = 'customer_register';
const PHONE_VERIFY_OTP_PURPOSE = 'phone_verify';

function setRefreshCookie(res: Response, refreshToken: string) {
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: process.env.COOKIE_SECURE === 'true',
    sameSite: (process.env.COOKIE_SAME_SITE as 'lax' | 'strict' | 'none') || 'lax',
    maxAge: 7 * 24 * 60 * 60 * 1000,
  });
}

async function issueAuthSession(
  res: Response,
  user: {
    id: string;
    email: string | null;
    phone: string | null;
    firstName: string;
    lastName: string;
    gender: string | null;
    role: 'CUSTOMER' | 'WORKER' | 'ADMIN';
    avatarUrl?: string | null;
    customerProfile?: { id: string } | null;
    workerProfile?: { id: string } | null;
  },
  message: string,
  statusCode = 200
) {
  const tokens = generateTokens(user.id, user.role, user.email || undefined);

  await prisma.user.update({
    where: { id: user.id },
    data: {
      refreshToken: tokens.refreshToken,
      lastLoginAt: new Date(),
      lastActiveAt: new Date(),
    },
  });

  setRefreshCookie(res, tokens.refreshToken);

  sendSuccess(
    res,
    {
      user: {
        id: user.id,
        email: user.email,
        phone: user.phone,
        firstName: user.firstName,
        lastName: user.lastName,
        gender: user.gender,
        role: user.role,
        avatarUrl: user.avatarUrl ?? undefined,
        customerProfile: user.customerProfile ?? undefined,
        workerProfile: user.workerProfile ?? undefined,
      },
      ...tokens,
    },
    message,
    statusCode
  );
}

async function assertEmailAvailable(email: string) {
  const normalized = email.trim().toLowerCase();
  const existing = await prisma.user.findFirst({
    where: { deletedAt: null, email: normalized },
  });
  if (existing) {
    throw new AppError('Email already registered', 409);
  }
  return normalized;
}

async function assertPhoneAvailable(phone: string, excludeUserId?: string) {
  const normalizedPhone = normalizePhilippinePhone(phone);
  const phoneVariants = philippinePhoneVariants(normalizedPhone);
  const existing = await prisma.user.findFirst({
    where: {
      deletedAt: null,
      phone: { in: phoneVariants },
      ...(excludeUserId ? { id: { not: excludeUserId } } : {}),
    },
  });
  if (existing) {
    throw new AppError('Phone number already registered', 409);
  }
  return normalizedPhone;
}

router.post(
  '/worker/check-email',
  validate(sendEmailOTPSchema),
  asyncHandler(async (req, res) => {
    const email = await assertEmailAvailable(req.body.email);
    sendSuccess(res, { available: true, email }, 'Email available');
  })
);

router.post(
  '/worker/send-otp',
  validate(sendEmailOTPSchema),
  asyncHandler(async (req, res) => {
    const email = await assertEmailAvailable(req.body.email);
    await createOTP(email, WORKER_REGISTER_OTP_PURPOSE);
    sendSuccess(res, { email }, 'OTP sent to your email');
  })
);

router.post(
  '/worker/resend-otp',
  validate(resendEmailOTPSchema),
  asyncHandler(async (req, res) => {
    const email = await assertEmailAvailable(req.body.email);
    await createOTP(email, WORKER_REGISTER_OTP_PURPOSE);
    sendSuccess(res, { email }, 'OTP resent successfully');
  })
);

router.post(
  '/customer/send-otp',
  validate(sendEmailOTPSchema),
  asyncHandler(async (req, res) => {
    const email = await assertEmailAvailable(req.body.email);
    await createOTP(email, CUSTOMER_REGISTER_OTP_PURPOSE);
    sendSuccess(res, { email }, 'OTP sent to your email');
  })
);

router.post(
  '/customer/resend-otp',
  validate(resendEmailOTPSchema),
  asyncHandler(async (req, res) => {
    const email = await assertEmailAvailable(req.body.email);
    await createOTP(email, CUSTOMER_REGISTER_OTP_PURPOSE);
    sendSuccess(res, { email }, 'OTP resent successfully');
  })
);

router.post(
  '/phone/send-otp',
  authenticate,
  validate(sendPhoneOTPSchema),
  asyncHandler(async (req: AuthRequest, res) => {
    const userId = req.user!.userId;
    const user = await prisma.user.findUnique({ where: { id: userId } });
    if (!user) throw new AppError('User not found', 404);
    if (user.phoneVerified && user.phone) {
      throw new AppError('Phone number is already verified', 400);
    }

    const normalizedPhone = await assertPhoneAvailable(req.body.phone, userId);
    await createOTP(normalizedPhone, PHONE_VERIFY_OTP_PURPOSE, userId);
    sendSuccess(res, { phone: normalizedPhone }, 'OTP sent to your phone');
  })
);

router.post(
  '/phone/verify',
  authenticate,
  validate(verifyPhoneOTPSchema),
  asyncHandler(async (req: AuthRequest, res) => {
    const userId = req.user!.userId;
    const normalizedPhone = await assertPhoneAvailable(req.body.phone, userId);

    const { valid } = await verifyOTP(
      normalizedPhone,
      req.body.otpCode,
      PHONE_VERIFY_OTP_PURPOSE
    );
    if (!valid) throw new AppError('Invalid or expired OTP', 400);

    const user = await prisma.user.update({
      where: { id: userId },
      data: {
        phone: normalizedPhone,
        phoneVerified: true,
      },
      select: {
        id: true,
        phone: true,
        phoneVerified: true,
        role: true,
        workerProfile: { select: { id: true } },
        customerProfile: { select: { id: true } },
      },
    });

    await syncOwnerVerification({
      workerId: user.workerProfile?.id ?? null,
      customerId: user.customerProfile?.id ?? null,
    });

    sendSuccess(
      res,
      { phone: user.phone, phoneVerified: user.phoneVerified },
      'Phone number verified'
    );
  })
);

router.post(
  '/google/profile',
  validate(googleAuthSchema),
  asyncHandler(async (req, res) => {
    const { idToken } = req.body;
    const identity = await verifyGoogleIdToken(idToken);

    const existing = await prisma.user.findFirst({
      where: {
        deletedAt: null,
        OR: [{ googleId: identity.googleId }, { email: identity.email }],
      },
      select: { id: true, role: true },
    });

    if (existing) {
      throw new AppError(
        'This Google account is already registered. Sign in instead, or use a different Google account.',
        409
      );
    }

    sendSuccess(
      res,
      {
        googleId: identity.googleId,
        email: identity.email,
        firstName: identity.firstName,
        lastName: identity.lastName,
        avatarUrl: identity.avatarUrl ?? null,
        emailVerified: identity.emailVerified,
      },
      'Google profile loaded'
    );
  })
);

router.post(
  '/register',
  validate(registerSchema),
  asyncHandler(async (req, res) => {
    const {
      email,
      phone,
      password,
      googleIdToken,
      firstName,
      lastName,
      gender,
      role,
      serviceAreas,
      categoryIds,
      otherSkills,
      isStudent,
      hourlyRate,
      rateType,
      displayName,
      otpCode,
    } = req.body;

    const googleIdentity = googleIdToken ? await verifyGoogleIdToken(googleIdToken) : null;
    const resolvedEmail = (googleIdentity?.email || email || undefined)?.toLowerCase();

    if (!resolvedEmail) {
      throw new AppError('Email is required', 400);
    }

    if (googleIdentity) {
      const googleTaken = await prisma.user.findFirst({
        where: {
          deletedAt: null,
          OR: [{ googleId: googleIdentity.googleId }, { email: googleIdentity.email }],
        },
      });
      if (googleTaken) {
        throw new AppError(
          'This Google account is already registered. Sign in instead.',
          409
        );
      }
    } else {
      await assertEmailAvailable(resolvedEmail);
    }

    // Optional phone at registration (usually added later in portal verification)
    let normalizedPhone: string | null = null;
    if (phone) {
      normalizedPhone = await assertPhoneAvailable(phone);
    }

    // Email OTP required unless Google Sign-In (Google already verified the email)
    if (!googleIdentity) {
      if (!otpCode) throw new AppError('OTP code is required', 400);
      const purpose =
        role === 'WORKER' ? WORKER_REGISTER_OTP_PURPOSE : CUSTOMER_REGISTER_OTP_PURPOSE;
      const { valid } = await verifyOTP(resolvedEmail, otpCode, purpose);
      if (!valid) throw new AppError('Invalid or expired OTP', 400);
    }

    let workerCategoryIds = categoryIds ?? [];
    if (role === 'WORKER' && otherSkills?.trim()) {
      const otherCategory = await prisma.category.findFirst({
        where: { slug: 'other', deletedAt: null },
      });
      if (otherCategory && !workerCategoryIds.includes(otherCategory.id)) {
        workerCategoryIds = [...workerCategoryIds, otherCategory.id];
      }
    }

    const passwordHash = password ? await bcrypt.hash(password, 12) : null;
    const user = await prisma.user.create({
      data: {
        email: resolvedEmail,
        phone: normalizedPhone,
        passwordHash,
        emailVerified: true,
        phoneVerified: false,
        ...(googleIdentity && {
          googleId: googleIdentity.googleId,
          avatarUrl: googleIdentity.avatarUrl,
        }),
        firstName: firstName || googleIdentity?.firstName || 'User',
        lastName: lastName || googleIdentity?.lastName || 'User',
        ...(gender && { gender }),
        role,
        ...(role === 'CUSTOMER'
          ? { customerProfile: { create: {} } }
          : {
              workerProfile: {
                create: {
                  displayName: String(displayName).trim(),
                  ...(hourlyRate !== undefined && { hourlyRate }),
                  ...(rateType && { rateType }),
                  ...(otherSkills?.trim() && { otherSkills: otherSkills.trim() }),
                  ...(isStudent !== undefined && { isStudent }),
                  serviceAreas: {
                    create: (serviceAreas?.length ? serviceAreas : ['DAVAO_CITY']).map(
                      (area: string) => ({ area })
                    ),
                  },
                  ...(workerCategoryIds.length && {
                    skills: {
                      create: workerCategoryIds.map((categoryId: string) => ({ categoryId })),
                    },
                  }),
                },
              },
            }),
      },
      select: {
        id: true,
        email: true,
        phone: true,
        firstName: true,
        lastName: true,
        gender: true,
        role: true,
      },
    });

    await issueAuthSession(res, user, 'Registration successful', 201);
  })
);

router.post(
  '/login',
  validate(loginSchema),
  asyncHandler(async (req, res) => {
    const { identifier, password } = req.body;

    let phoneVariants: string[] = [];
    if (!identifier.includes('@')) {
      try {
        phoneVariants = philippinePhoneVariants(identifier);
      } catch {
        phoneVariants = [];
      }
    }

    const user = await prisma.user.findFirst({
      where: {
        OR: [
          { email: identifier },
          { phone: identifier },
          ...phoneVariants.map((phone) => ({ phone })),
        ],
        status: 'ACTIVE',
        deletedAt: null,
      },
      include: {
        customerProfile: { select: { id: true } },
        workerProfile: { select: { id: true } },
      },
    });

    if (!user) {
      throw new AppError('Invalid credentials', 401);
    }

    if (!user.passwordHash) {
      if (user.googleId) {
        throw new AppError('This account uses Google Sign-In. Continue with Google instead.', 401);
      }
      throw new AppError('Invalid credentials', 401);
    }

    const valid = await bcrypt.compare(password, user.passwordHash);
    if (!valid) {
      throw new AppError('Invalid credentials', 401);
    }

    await issueAuthSession(res, user, 'Login successful');
  })
);

router.post(
  '/google',
  validate(googleAuthSchema),
  asyncHandler(async (req, res) => {
    const { idToken } = req.body;
    const identity = await verifyGoogleIdToken(idToken);

    let user = await prisma.user.findFirst({
      where: {
        deletedAt: null,
        OR: [{ googleId: identity.googleId }, { email: identity.email }],
      },
      include: {
        customerProfile: { select: { id: true } },
        workerProfile: { select: { id: true } },
      },
    });

    if (user) {
      if (user.status !== 'ACTIVE') {
        throw new AppError('Account is not active', 403);
      }

      const updates: {
        googleId?: string;
        emailVerified?: boolean;
        avatarUrl?: string;
        email?: string;
      } = {};

      if (!user.googleId) updates.googleId = identity.googleId;
      if (!user.emailVerified) updates.emailVerified = true;
      if (!user.avatarUrl && identity.avatarUrl) updates.avatarUrl = identity.avatarUrl;
      if (!user.email) updates.email = identity.email;

      if (Object.keys(updates).length > 0) {
        user = await prisma.user.update({
          where: { id: user.id },
          data: updates,
          include: {
            customerProfile: { select: { id: true } },
            workerProfile: { select: { id: true } },
          },
        });
      }

      await issueAuthSession(res, user, 'Login successful');
      return;
    }

    throw new AppError(
      'No account found for this Google account. Please register first.',
      404
    );
  })
);

router.post(
  '/refresh',
  validate(refreshSchema),
  asyncHandler(async (req, res) => {
    const refreshToken = req.body.refreshToken || req.cookies?.refreshToken;
    if (!refreshToken) {
      throw new AppError('Refresh token required', 401);
    }

    const payload = verifyRefreshToken(refreshToken);
    const user = await prisma.user.findFirst({
      where: { id: payload.userId, refreshToken, status: 'ACTIVE', deletedAt: null },
    });

    if (!user) {
      throw new AppError('Invalid refresh token', 401);
    }

    const tokens = generateTokens(user.id, user.role, user.email || undefined);

    await prisma.user.update({
      where: { id: user.id },
      data: { refreshToken: tokens.refreshToken },
    });

    setRefreshCookie(res, tokens.refreshToken);

    sendSuccess(res, tokens, 'Token refreshed');
  })
);

router.post(
  '/logout',
  authenticate,
  asyncHandler(async (req: AuthRequest, res: Response) => {
    await prisma.user.update({
      where: { id: req.user!.userId },
      data: { refreshToken: null },
    });
    res.clearCookie('refreshToken');
    sendSuccess(res, null, 'Logged out successfully');
  })
);

router.get(
  '/me',
  authenticate,
  asyncHandler(async (req: AuthRequest, res: Response) => {
    const user = await prisma.user.findUnique({
      where: { id: req.user!.userId },
      select: {
        id: true,
        email: true,
        phone: true,
        firstName: true,
        lastName: true,
        gender: true,
        role: true,
        avatarUrl: true,
        emailVerified: true,
        phoneVerified: true,
        deletionRequestedAt: true,
        deletionScheduledFor: true,
        deletionReason: true,
        customerProfile: true,
        workerProfile: {
          include: {
            skills: { include: { category: true } },
            serviceAreas: true,
            availability: true,
          },
        },
      },
    });

    if (!user) throw new AppError('User not found', 404);
    sendSuccess(res, user);
  })
);

export default router;
