import crypto from 'crypto';
import { prisma } from '../../config/database';
import { env } from '../../config/env';
import { AppError } from '../../middleware/error-handler';
import { logger } from '../../utils/logger';

/** Derive a domain-separated key for ticket HMACs (not the raw ENCRYPTION_KEY). */
function getTicketHmacKey(): Buffer {
  return crypto.createHmac('sha256', env.ENCRYPTION_KEY).update('ticket-hmac-v1').digest();
}

/** Generate a human-readable ticket code like "ABCD-1234" */
function generateTicketCode(): string {
  const letters = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // No I, O for readability
  const part1 = Array.from({ length: 4 }, () => letters[crypto.randomInt(letters.length)]).join('');
  const part2 = String(crypto.randomInt(1000, 9999));
  return `${part1}-${part2}`;
}

/** Generate HMAC token for QR code validation */
function generateToken(ticketId: string): { token: string; tokenHash: string } {
  const nonce = crypto.randomBytes(16);
  const hmac = crypto.createHmac('sha256', getTicketHmacKey());
  hmac.update(ticketId);
  hmac.update(nonce);
  const token = Buffer.concat([
    Buffer.from(ticketId),
    Buffer.from(':'),
    nonce,
    Buffer.from(':'),
    hmac.digest(),
  ]).toString('base64url');

  const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
  return { token, tokenHash };
}

/** Hash an incoming token for lookup */
function hashToken(token: string): string {
  return crypto.createHash('sha256').update(token).digest('hex');
}

export const ticketsService = {
  /**
   * Create one or more tickets for an event + tier.
   * Used by both free registration and post-checkout webhook.
   */
  async createTickets(params: {
    eventId: string;
    tierId: string;
    quantity: number;
    holderEmail: string;
    holderName?: string;
    userId?: string;
    orderId?: string;
  }) {
    const { eventId, tierId, quantity, holderEmail, holderName, userId, orderId } = params;

    // Validate tier exists and belongs to event
    const tier = await prisma.ticketTier.findUnique({ where: { id: tierId } });
    if (!tier || tier.eventId !== eventId) {
      throw new AppError(400, 'Invalid tier for this event', 'INVALID_TIER');
    }

    // Check quantity limits
    if (quantity > tier.maxPerOrder) {
      throw new AppError(400, `Maximum ${tier.maxPerOrder} tickets per order`, 'MAX_PER_ORDER');
    }

    // Atomically check + reserve capacity using a transaction
    const tickets = await prisma.$transaction(async (tx) => {
      // Lock the tier row and check availability
      const currentTier = await tx.ticketTier.findUnique({ where: { id: tierId } });
      if (!currentTier) throw new AppError(400, 'Tier not found', 'NOT_FOUND');

      if (currentTier.maxQuantity && currentTier.soldCount + quantity > currentTier.maxQuantity) {
        throw new AppError(400, 'Not enough tickets available', 'SOLD_OUT');
      }

      // Check event-level capacity
      const event = await tx.ticketedEvent.findUnique({ where: { id: eventId } });
      if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');

      if (event.maxAttendees && event.currentAttendees + quantity > event.maxAttendees) {
        throw new AppError(400, 'Event is at full capacity', 'SOLD_OUT');
      }

      // Generate tickets
      const ticketData = [];
      for (let i = 0; i < quantity; i++) {
        let ticketCode = generateTicketCode();
        // Ensure uniqueness (extremely unlikely collision but safe)
        while (await tx.ticket.findUnique({ where: { ticketCode } })) {
          ticketCode = generateTicketCode();
        }

        const { token, tokenHash } = generateToken(`${eventId}:${ticketCode}`);
        ticketData.push({
          ticketCode,
          tokenHash,
          token, // Not stored — returned for QR generation
          eventId,
          tierId,
          orderId,
          holderEmail,
          holderName,
          userId,
        });
      }

      // Create all tickets
      const created = [];
      for (const td of ticketData) {
        const { token, ...dbData } = td;
        const ticket = await tx.ticket.create({ data: dbData });
        created.push({ ...ticket, token });
      }

      // Increment sold count + attendee count
      await tx.ticketTier.update({
        where: { id: tierId },
        data: { soldCount: { increment: quantity } },
      });
      await tx.ticketedEvent.update({
        where: { id: eventId },
        data: { currentAttendees: { increment: quantity } },
      });

      return created;
    });

    return tickets;
  },

  /** Validate a QR token — returns ticket info without marking checked in */
  async validateToken(token: string) {
    const hash = hashToken(token);
    const ticket = await prisma.ticket.findUnique({
      where: { tokenHash: hash },
      include: {
        event: { select: { id: true, title: true, slug: true, date: true, startTime: true } },
        tier: { select: { name: true, tierType: true } },
      },
    });

    if (!ticket) {
      return { valid: false, error: 'Invalid ticket' };
    }

    if (ticket.status === 'CHECKED_IN') {
      return {
        valid: false,
        error: 'Already checked in',
        ticket: {
          id: ticket.id,
          ticketCode: ticket.ticketCode,
          holderName: ticket.holderName,
          holderEmail: ticket.holderEmail,
          status: ticket.status,
          checkedInAt: ticket.checkedInAt,
          event: ticket.event,
          tier: ticket.tier,
        },
      };
    }

    if (ticket.status === 'CANCELLED' || ticket.status === 'REFUNDED') {
      return {
        valid: false,
        error: `Ticket is ${ticket.status.toLowerCase()}`,
        ticket: {
          id: ticket.id,
          ticketCode: ticket.ticketCode,
          status: ticket.status,
        },
      };
    }

    return {
      valid: true,
      ticket: {
        id: ticket.id,
        ticketCode: ticket.ticketCode,
        holderName: ticket.holderName,
        holderEmail: ticket.holderEmail,
        status: ticket.status,
        event: ticket.event,
        tier: ticket.tier,
      },
    };
  },

  /** Confirm check-in — marks ticket as CHECKED_IN */
  async confirmCheckin(token: string, checkedInByUserId?: string, notes?: string) {
    const hash = hashToken(token);
    const ticket = await prisma.ticket.findUnique({
      where: { tokenHash: hash },
      include: {
        event: { select: { id: true, title: true } },
        tier: { select: { name: true } },
      },
    });

    if (!ticket) throw new AppError(400, 'Invalid ticket', 'INVALID_TICKET');
    if (ticket.status === 'CHECKED_IN') throw new AppError(400, 'Ticket already checked in', 'ALREADY_CHECKED_IN');
    if (ticket.status !== 'VALID') throw new AppError(400, `Ticket is ${ticket.status.toLowerCase()}`, 'INVALID_STATUS');

    const [updatedTicket, checkIn] = await prisma.$transaction([
      prisma.ticket.update({
        where: { id: ticket.id },
        data: {
          status: 'CHECKED_IN',
          checkedInAt: new Date(),
          checkedInByUserId,
        },
      }),
      prisma.checkIn.create({
        data: {
          ticketId: ticket.id,
          eventId: ticket.eventId,
          checkedInByUserId,
          method: 'QR',
          notes,
        },
      }),
    ]);

    return {
      ticket: updatedTicket,
      checkIn,
      event: ticket.event,
      tier: ticket.tier,
    };
  },

  /** Manual check-in by ticket code or email */
  async manualCheckin(params: {
    eventId: string;
    ticketCode?: string;
    holderEmail?: string;
    checkedInByUserId?: string;
    notes?: string;
  }) {
    const { eventId, ticketCode, holderEmail, checkedInByUserId, notes } = params;

    let ticket;
    if (ticketCode) {
      ticket = await prisma.ticket.findUnique({
        where: { ticketCode },
        include: {
          event: { select: { id: true, title: true } },
          tier: { select: { name: true } },
        },
      });
    } else if (holderEmail) {
      ticket = await prisma.ticket.findFirst({
        where: { eventId, holderEmail, status: 'VALID' },
        include: {
          event: { select: { id: true, title: true } },
          tier: { select: { name: true } },
        },
      });
    }

    if (!ticket) throw new AppError(404, 'Ticket not found', 'NOT_FOUND');
    if (ticket.eventId !== eventId) throw new AppError(400, 'Ticket does not belong to this event', 'WRONG_EVENT');
    if (ticket.status === 'CHECKED_IN') throw new AppError(400, 'Ticket already checked in', 'ALREADY_CHECKED_IN');
    if (ticket.status !== 'VALID') throw new AppError(400, `Ticket is ${ticket.status.toLowerCase()}`, 'INVALID_STATUS');

    const [updatedTicket, checkIn] = await prisma.$transaction([
      prisma.ticket.update({
        where: { id: ticket.id },
        data: {
          status: 'CHECKED_IN',
          checkedInAt: new Date(),
          checkedInByUserId,
        },
      }),
      prisma.checkIn.create({
        data: {
          ticketId: ticket.id,
          eventId,
          checkedInByUserId,
          method: ticketCode ? 'CODE' : 'MANUAL',
          notes,
        },
      }),
    ]);

    return {
      ticket: updatedTicket,
      checkIn,
      event: ticket.event,
      tier: ticket.tier,
    };
  },

  /** Cancel a specific ticket */
  async cancelTicket(ticketId: string) {
    const ticket = await prisma.ticket.findUnique({ where: { id: ticketId } });
    if (!ticket) throw new AppError(404, 'Ticket not found', 'NOT_FOUND');
    if (ticket.status !== 'VALID') {
      throw new AppError(400, `Cannot cancel ticket in ${ticket.status} status`, 'INVALID_STATUS');
    }

    return prisma.$transaction([
      prisma.ticket.update({
        where: { id: ticketId },
        data: { status: 'CANCELLED' },
      }),
      prisma.ticketTier.update({
        where: { id: ticket.tierId },
        data: { soldCount: { decrement: 1 } },
      }),
      prisma.ticketedEvent.update({
        where: { id: ticket.eventId },
        data: { currentAttendees: { decrement: 1 } },
      }),
    ]);
  },

  /** Get user's tickets */
  async getUserTickets(userId: string) {
    return prisma.ticket.findMany({
      where: { userId },
      orderBy: { issuedAt: 'desc' },
      include: {
        event: {
          select: {
            id: true, slug: true, title: true, date: true,
            startTime: true, endTime: true, venueName: true, venueAddress: true,
          },
        },
        tier: { select: { name: true, tierType: true, priceCAD: true } },
      },
    });
  },

  /** Get user's tickets for a specific email (for guests) */
  async getTicketsByEmail(email: string) {
    return prisma.ticket.findMany({
      where: { holderEmail: email },
      orderBy: { issuedAt: 'desc' },
      include: {
        event: {
          select: {
            id: true, slug: true, title: true, date: true,
            startTime: true, endTime: true, venueName: true,
          },
        },
        tier: { select: { name: true, tierType: true } },
      },
    });
  },

  /** Find a ticket by code (for confirmation page) */
  async findByCode(ticketCode: string) {
    const ticket = await prisma.ticket.findUnique({
      where: { ticketCode },
      include: {
        event: {
          select: {
            id: true, slug: true, title: true, date: true,
            startTime: true, endTime: true, venueName: true, venueAddress: true,
            organizerName: true,
          },
        },
        tier: { select: { name: true, tierType: true, priceCAD: true } },
      },
    });
    if (!ticket) throw new AppError(404, 'Ticket not found', 'NOT_FOUND');
    return ticket;
  },
};
