import mongoose from 'mongoose';
import cloudinary from '../config/cloudinary';
import CertificateRequest, { CertificateRequestStatus } from '../models/certificateRequest';
import UserCertificate from '../models/userCertificate';
import { createNotification } from './notificationService';

const badRequest = (msg: string): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: 400 });

const notFound = (msg: string): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: 404 });

const validateDates = (issueDate: string, completionDate?: string) => {
  const issue = new Date(issueDate);
  if (Number.isNaN(issue.getTime())) throw badRequest('issue_date must be a valid date');

  if (!completionDate) return { issue, completion: null as Date | null };

  const completion = new Date(completionDate);
  if (Number.isNaN(completion.getTime())) throw badRequest('completion_date must be a valid date');
  if (completion < issue) throw badRequest('completion_date must be on or after issue_date');

  return { issue, completion };
};

const uploadCertificateFile = async (fileBuffer: Buffer, mimetype: string) => {
  const base64 = fileBuffer.toString('base64');
  const dataUri = `data:${mimetype};base64,${base64}`;

  const uploaded = await cloudinary.uploader.upload(dataUri, {
    folder: 'certificates',
    resource_type: 'auto',
  });

  return {
    url: uploaded.secure_url,
    publicId: uploaded.public_id,
  };
};

export const submitCertificateRequest = async (
  userId: string,
  payload: {
    certificate_id: string;
    title: string;
    description?: string;
    issuer: string;
    issue_date: string;
    completion_date?: string;
  },
  file: { buffer: Buffer; mimetype: string },
) => {
  const certificateId = payload.certificate_id.trim().toUpperCase();
  const title = payload.title.trim();
  const issuer = payload.issuer.trim();
  if (!certificateId) throw badRequest('certificate_id is required');
  if (!title) throw badRequest('title is required');
  if (!issuer) throw badRequest('issuer is required');

  if (!/^[A-Z0-9_-]{4,30}$/.test(certificateId)) {
    throw badRequest('certificate_id must be 4-30 chars and contain only letters, numbers, _ or -');
  }

  const { issue, completion } = validateDates(payload.issue_date, payload.completion_date);
  const upload = await uploadCertificateFile(file.buffer, file.mimetype);

  try {
    const request = await CertificateRequest.create({
      user: userId,
      certificate_id: certificateId,
      title,
      description: payload.description?.trim() || null,
      issuer,
      issue_date: issue,
      completion_date: completion,
      certificate_url: upload.url,
      certificate_public_id: upload.publicId,
      status: 'pending',
    });
    return request;
  } catch (err: any) {
    if (err.code === 11000 && err.keyPattern && err.keyPattern.certificate_id) {
      throw badRequest('A certificate with this certificate_id already exists. Please use a unique certificate_id.');
    }
    throw err;
  }
};


export const getMyCertificateRequests = async (
  userId: string,
  status: CertificateRequestStatus | undefined,
  page: number = 1,
  limit: number = 10
) => {
  const filter: Record<string, unknown> = { user: userId };
  if (status) filter.status = status;
  const skip = (page - 1) * limit;
  const [requests, total] = await Promise.all([
    CertificateRequest.find(filter)
      .select('certificate_id title description issuer issue_date completion_date certificate_url status admin_comment reviewed_at createdAt')
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit)
      .lean(),
    CertificateRequest.countDocuments(filter),
  ]);
  const totalPages = Math.ceil(total / limit);
  return {
    requests,
    pagination: {
      total,
      page,
      limit,
      totalPages,
      hasNextPage: page < totalPages,
      hasPrevPage: page > 1,
    },
  };
};


export const getMyApprovedCertificates = async (
  userId: string,
  page: number = 1,
  limit: number = 10
) => {
  const filter: Record<string, unknown> = { user: userId };
  const skip = (page - 1) * limit;
  const [certificates, total] = await Promise.all([
    UserCertificate.find(filter)
      .select('certificate_id title description issuer issue_date completion_date certificate_url createdAt')
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit)
      .lean(),
    UserCertificate.countDocuments(filter),
  ]);
  const totalPages = Math.ceil(total / limit);
  return {
    certificates,
    pagination: {
      total,
      page,
      limit,
      totalPages,
      hasNextPage: page < totalPages,
      hasPrevPage: page > 1,
    },
  };
};

export const getCertificateRequestsForAdmin = async (status: CertificateRequestStatus | undefined, page = 1, limit = 10) => {
  const filter: Record<string, unknown> = {};
  if (status) filter.status = status;

  const skip = (page - 1) * limit;

  const [requests, total] = await Promise.all([
    CertificateRequest.find(filter)
      .populate('user', 'name email department designation')
      .populate('reviewed_by', 'name email')
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit)
      .lean(),
    CertificateRequest.countDocuments(filter),
  ]);

  const totalPages = Math.ceil(total / limit);

  return {
    requests,
    pagination: {
      total,
      page,
      limit,
      totalPages,
      hasNextPage: page < totalPages,
      hasPrevPage: page > 1,
    },
  };
};

export const getCertificateRequestByIdForAdmin = async (requestId: string) => {
  const request = await CertificateRequest.findById(requestId)
    .populate('user', 'name email department designation employee_id')
    .populate('reviewed_by', 'name email')
    .lean();

  if (!request) throw notFound('Certificate request not found');
  return request;
};

export const approveCertificateRequest = async (requestId: string, adminId: string, adminComment?: string) => {
  const session = await mongoose.startSession();
  session.startTransaction();

  try {
    const request = await CertificateRequest.findById(requestId).session(session);
    if (!request) throw notFound('Certificate request not found');
    if (request.status !== 'pending') throw badRequest('Only pending requests can be approved');

    request.status = 'approved';
    request.admin_comment = adminComment?.trim() || null;
    request.reviewed_by = new mongoose.Types.ObjectId(adminId);
    request.reviewed_at = new Date();
    await request.save({ session });

    const certificate = await UserCertificate.create(
      [{
        certificate_id: request.certificate_id,
        user: request.user,
        request: request._id,
        title: request.title,
        description: request.description,
        issuer: request.issuer,
        issue_date: request.issue_date,
        completion_date: request.completion_date,
        certificate_url: request.certificate_url,
        certificate_public_id: request.certificate_public_id,
      }],
      { session },
    );

    await session.commitTransaction();

    await createNotification({
      userId: request.user.toString(),
      title: 'Certificate approved',
      message: `${request.title} has been approved by admin.`,
      type: 'CERTIFICATE_APPROVED',
      eventKey: `CERTIFICATE_APPROVED:${request._id.toString()}`,
      meta: { certificateRequestId: request._id.toString() },
    });

    return { request, certificate: certificate[0] };
  } catch (error) {
    await session.abortTransaction();
    throw error;
  } finally {
    session.endSession();
  }
};

export const rejectCertificateRequest = async (requestId: string, adminId: string, adminComment?: string) => {
  const request = await CertificateRequest.findById(requestId);
  if (!request) throw notFound('Certificate request not found');
  if (request.status !== 'pending') throw badRequest('Only pending requests can be rejected');

  request.status = 'rejected';
  request.admin_comment = adminComment?.trim() || null;
  request.reviewed_by = new mongoose.Types.ObjectId(adminId);
  request.reviewed_at = new Date();
  await request.save();

  await createNotification({
    userId: request.user.toString(),
    title: 'Certificate rejected',
    message: `${request.title} was rejected.${request.admin_comment ? ` Reason: ${request.admin_comment}` : ''}`,
    type: 'CERTIFICATE_REJECTED',
    eventKey: `CERTIFICATE_REJECTED:${request._id.toString()}`,
    meta: { certificateRequestId: request._id.toString() },
  });

  return request;
};
