import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, dbGet, dbAll, users, tickets, payments, events, invoices } from '../db/index.js';
import { eq, desc, and, gt, sql, inArray } from 'drizzle-orm';
import { requireAuth, getUserPasswordHash, hasGoogleAccount, validatePassword, type AuthUser } from '../lib/auth.js';
import { auth } from '../lib/betterAuth.js';
import { authSessions, authAccounts } from '../db/auth-schema.js';
import { getNow } from '../lib/utils.js';

const dashboard = new Hono();

// Apply authentication to all routes
dashboard.use('*', requireAuth());

// ==================== Profile Routes ====================

const updateProfileSchema = z.object({
  name: z.string().min(2).optional(),
  phone: z.string().optional(),
  languagePreference: z.enum(['en', 'es']).optional(),
  rucNumber: z.string().max(15).optional(),
});

// Get user profile
dashboard.get('/profile', async (c) => {
  const user = (c as any).get('user') as AuthUser;
  
  // Get membership duration
  const createdDate = new Date(user.createdAt);
  const now = new Date();
  const membershipDays = Math.floor((now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24));
  
  const hasPassword = !!(await getUserPasswordHash(user.id));

  return c.json({
    profile: {
      id: user.id,
      email: user.email,
      name: user.name,
      phone: user.phone,
      languagePreference: user.languagePreference,
      rucNumber: user.rucNumber,
      isClaimed: user.isClaimed,
      accountStatus: user.accountStatus,
      hasPassword,
      hasGoogleLinked: await hasGoogleAccount(user.id),
      memberSince: user.createdAt,
      membershipDays,
      createdAt: user.createdAt,
    },
  });
});

// Update profile
dashboard.put('/profile', zValidator('json', updateProfileSchema), async (c) => {
  const user = (c as any).get('user') as AuthUser;
  const data = c.req.valid('json');
  const now = getNow();
  
  await (db as any)
    .update(users)
    .set({
      ...data,
      updatedAt: now,
    })
    .where(eq((users as any).id, user.id));
  
  const updatedUser = await dbGet<any>(
    (db as any)
      .select()
      .from(users)
      .where(eq((users as any).id, user.id))
  );
  
  return c.json({
    profile: {
      id: updatedUser.id,
      email: updatedUser.email,
      name: updatedUser.name,
      phone: updatedUser.phone,
      languagePreference: updatedUser.languagePreference,
      rucNumber: updatedUser.rucNumber,
    },
    message: 'Profile updated successfully',
  });
});

// ==================== Tickets Routes ====================

// Get user's tickets
dashboard.get('/tickets', async (c) => {
  const user = (c as any).get('user') as AuthUser;
  
  const userTickets = await dbAll<any>(
    (db as any)
      .select()
      .from(tickets)
      .where(eq((tickets as any).userId, user.id))
      .orderBy(desc((tickets as any).createdAt))
  );

  // Batch-fetch related events, payments, and invoices (avoids N+1 per ticket).
  const eventIds = [...new Set(userTickets.map((t: any) => t.eventId).filter(Boolean))];
  const ticketIds = userTickets.map((t: any) => t.id);

  const eventRows = eventIds.length
    ? await dbAll<any>((db as any).select().from(events).where(inArray((events as any).id, eventIds)))
    : [];
  const paymentRows = ticketIds.length
    ? await dbAll<any>((db as any).select().from(payments).where(inArray((payments as any).ticketId, ticketIds)))
    : [];

  const eventsById = new Map(eventRows.map((e: any) => [e.id, e]));
  const paymentsByTicketId = new Map(paymentRows.map((p: any) => [p.ticketId, p]));

  const paidPaymentIds = paymentRows.filter((p: any) => p.status === 'paid').map((p: any) => p.id);
  const invoiceRows = paidPaymentIds.length
    ? await dbAll<any>((db as any).select().from(invoices).where(inArray((invoices as any).paymentId, paidPaymentIds)))
    : [];
  const invoicesByPaymentId = new Map(invoiceRows.map((inv: any) => [inv.paymentId, inv]));
  
  const ticketsWithEvents = userTickets.map((ticket: any) => {
      const event = eventsById.get(ticket.eventId);
      const payment = paymentsByTicketId.get(ticket.id);
      const invoice = payment && payment.status === 'paid' ? invoicesByPaymentId.get(payment.id) : null;
      
      return {
        ...ticket,
        event: event ? {
          id: event.id,
          title: event.title,
          titleEs: event.titleEs,
          startDatetime: event.startDatetime,
          endDatetime: event.endDatetime,
          location: event.location,
          locationUrl: event.locationUrl,
          price: event.price,
          currency: event.currency,
          status: event.status,
          bannerUrl: event.bannerUrl,
        } : null,
        payment: payment ? {
          id: payment.id,
          provider: payment.provider,
          amount: payment.amount,
          currency: payment.currency,
          status: payment.status,
          paidAt: payment.paidAt,
        } : null,
        invoice: invoice ? {
          id: invoice.id,
          invoiceNumber: invoice.invoiceNumber,
          pdfUrl: invoice.pdfUrl,
          createdAt: invoice.createdAt,
        } : null,
      };
    });
  
  return c.json({ tickets: ticketsWithEvents });
});

// Get single ticket detail
dashboard.get('/tickets/:id', async (c) => {
  const user = (c as any).get('user') as AuthUser;
  const ticketId = c.req.param('id');
  
  const ticket = await dbGet<any>(
    (db as any)
      .select()
      .from(tickets)
      .where(
        and(
          eq((tickets as any).id, ticketId),
          eq((tickets as any).userId, user.id)
        )
      )
  );
  
  if (!ticket) {
    return c.json({ error: 'Ticket not found' }, 404);
  }
  
  const event = await dbGet(
    (db as any)
      .select()
      .from(events)
      .where(eq((events as any).id, ticket.eventId))
  );
  
  const payment = await dbGet<any>(
    (db as any)
      .select()
      .from(payments)
      .where(eq((payments as any).ticketId, ticket.id))
  );
  
  let invoice = null;
  if (payment && payment.status === 'paid') {
    invoice = await dbGet(
      (db as any)
        .select()
        .from(invoices)
        .where(eq((invoices as any).paymentId, payment.id))
    );
  }
  
  return c.json({
    ticket: {
      ...ticket,
      event,
      payment,
      invoice,
    },
  });
});

// ==================== Next Event Route ====================

// Get next upcoming event for user
dashboard.get('/next-event', async (c) => {
  const user = (c as any).get('user') as AuthUser;
  const now = getNow();
  
  // Get user's tickets for upcoming events
  const userTickets = await dbAll<any>(
    (db as any)
      .select()
      .from(tickets)
      .where(eq((tickets as any).userId, user.id))
  );
  
  if (userTickets.length === 0) {
    return c.json({ nextEvent: null });
  }
  
  // Find the next upcoming event
  let nextEvent = null;
  let nextTicket = null;
  let nextPayment = null;
  
  for (const ticket of userTickets) {
    if (ticket.status === 'cancelled') continue;
    
    const event = await dbGet<any>(
      (db as any)
        .select()
        .from(events)
        .where(eq((events as any).id, ticket.eventId))
    );
    
    if (!event) continue;
    
    // Check if event is in the future
    if (new Date(event.startDatetime) > new Date()) {
      if (!nextEvent || new Date(event.startDatetime) < new Date(nextEvent.startDatetime)) {
        nextEvent = event;
        nextTicket = ticket;
        nextPayment = await dbGet(
          (db as any)
            .select()
            .from(payments)
            .where(eq((payments as any).ticketId, ticket.id))
        );
      }
    }
  }
  
  if (!nextEvent) {
    return c.json({ nextEvent: null });
  }
  
  return c.json({
    nextEvent: {
      event: nextEvent,
      ticket: nextTicket,
      payment: nextPayment,
    },
  });
});

// ==================== Payments & Invoices Routes ====================

// Get payment history
dashboard.get('/payments', async (c) => {
  const user = (c as any).get('user') as AuthUser;
  
  // Get all user's tickets first
  const userTickets = await dbAll<any>(
    (db as any)
      .select()
      .from(tickets)
      .where(eq((tickets as any).userId, user.id))
  );
  
  const ticketIds = userTickets.map((t: any) => t.id);
  
  if (ticketIds.length === 0) {
    return c.json({ payments: [] });
  }
  
  // Get all payments for user's tickets
  const allPayments = [];
  for (const ticketId of ticketIds) {
    const ticketPayments = await dbAll<any>(
      (db as any)
        .select()
        .from(payments)
        .where(eq((payments as any).ticketId, ticketId))
    );
    
    for (const payment of ticketPayments) {
      const ticket = userTickets.find((t: any) => t.id === payment.ticketId);
      const event = ticket
        ? await dbGet<any>(
            (db as any)
              .select()
              .from(events)
              .where(eq((events as any).id, ticket.eventId))
          )
        : null;
      
      let invoice: any = null;
      if (payment.status === 'paid') {
        invoice = await dbGet<any>(
          (db as any)
            .select()
            .from(invoices)
            .where(eq((invoices as any).paymentId, payment.id))
        );
      }
      
      allPayments.push({
        ...payment,
        ticket: ticket ? {
          id: ticket.id,
          attendeeFirstName: ticket.attendeeFirstName,
          attendeeLastName: ticket.attendeeLastName,
          status: ticket.status,
        } : null,
        event: event ? {
          id: event.id,
          title: event.title,
          titleEs: event.titleEs,
          startDatetime: event.startDatetime,
        } : null,
        invoice: invoice ? {
          id: invoice.id,
          invoiceNumber: invoice.invoiceNumber,
          pdfUrl: invoice.pdfUrl,
        } : null,
      });
    }
  }
  
  // Sort by createdAt desc
  allPayments.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
  
  return c.json({ payments: allPayments });
});

// Get invoices
dashboard.get('/invoices', async (c) => {
  const user = (c as any).get('user') as AuthUser;
  
  const userInvoices = await dbAll<any>(
    (db as any)
      .select()
      .from(invoices)
      .where(eq((invoices as any).userId, user.id))
      .orderBy(desc((invoices as any).createdAt))
  );
  
  // Get payment and event details for each invoice
  const invoicesWithDetails = await Promise.all(
    userInvoices.map(async (invoice: any) => {
      const payment = await dbGet<any>(
        (db as any)
          .select()
          .from(payments)
          .where(eq((payments as any).id, invoice.paymentId))
      );
      
      let event: any = null;
      if (payment) {
        const ticket = await dbGet<any>(
          (db as any)
            .select()
            .from(tickets)
            .where(eq((tickets as any).id, payment.ticketId))
        );
        
        if (ticket) {
          event = await dbGet<any>(
            (db as any)
              .select()
              .from(events)
              .where(eq((events as any).id, ticket.eventId))
          );
        }
      }
      
      return {
        ...invoice,
        event: event ? {
          id: event.id,
          title: event.title,
          titleEs: event.titleEs,
          startDatetime: event.startDatetime,
        } : null,
      };
    })
  );
  
  return c.json({ invoices: invoicesWithDetails });
});

// ==================== Security Routes ====================

// Get active sessions (Better Auth session table; validated per-request so
// this list is always live). Session tokens are never exposed to the client.
dashboard.get('/sessions', async (c) => {
  const user = (c as any).get('user') as AuthUser;

  const sessions = await dbAll<any>(
    (db as any)
      .select({
        id: (authSessions as any).id,
        userAgent: (authSessions as any).userAgent,
        ipAddress: (authSessions as any).ipAddress,
        createdAt: (authSessions as any).createdAt,
        updatedAt: (authSessions as any).updatedAt,
        expiresAt: (authSessions as any).expiresAt,
      })
      .from(authSessions)
      .where(
        and(
          eq((authSessions as any).userId, user.id),
          gt((authSessions as any).expiresAt, new Date())
        )
      )
      .orderBy(desc((authSessions as any).updatedAt))
  );

  return c.json({
    sessions: sessions.map((s: any) => ({
      id: s.id,
      userAgent: s.userAgent,
      ipAddress: s.ipAddress,
      lastActiveAt: s.updatedAt,
      createdAt: s.createdAt,
      expiresAt: s.expiresAt,
      current: s.id === user.sessionId,
    })),
  });
});

// Revoke a specific session. Deleting the row is immediately effective:
// sessions are validated against the table on every request (no cookie cache).
dashboard.delete('/sessions/:id', async (c) => {
  const user = (c as any).get('user') as AuthUser;
  const sessionId = c.req.param('id');

  await (db as any)
    .delete(authSessions)
    .where(
      and(
        eq((authSessions as any).id, sessionId),
        eq((authSessions as any).userId, user.id)
      )
    );

  return c.json({ message: 'Session revoked' });
});

// Revoke all other sessions (logout everywhere else); the current session
// stays valid so this device remains signed in.
dashboard.post('/sessions/revoke-all', async (c) => {
  const user = (c as any).get('user') as AuthUser;

  await (db as any)
    .delete(authSessions)
    .where(
      and(
        eq((authSessions as any).userId, user.id),
        sql`${(authSessions as any).id} != ${user.sessionId}`
      )
    );

  return c.json({ message: 'All other sessions revoked.' });
});

// Set password (for users without one)
const setPasswordSchema = z.object({
  password: z.string().min(10, 'Password must be at least 10 characters'),
});

dashboard.post('/set-password', zValidator('json', setPasswordSchema), async (c) => {
  const user = (c as any).get('user') as AuthUser;
  const { password } = c.req.valid('json');

  // Check if user already has a password
  if (await getUserPasswordHash(user.id)) {
    return c.json({ error: 'Password already set. Use change password instead.' }, 400);
  }

  // setPassword is a server-only Better Auth endpoint, so the HTTP-layer
  // policy hook does not cover it — validate explicitly.
  const passwordValidation = validatePassword(password);
  if (!passwordValidation.valid) {
    return c.json({ error: passwordValidation.error }, 400);
  }

  try {
    await auth.api.setPassword({
      body: { newPassword: password },
      headers: c.req.raw.headers,
    });
  } catch (err: any) {
    return c.json({ error: err?.body?.message || 'Failed to set password' }, 400);
  }

  return c.json({ message: 'Password set successfully' });
});

// Unlink Google account (only if password is set)
dashboard.post('/unlink-google', async (c) => {
  const user = (c as any).get('user') as AuthUser;

  if (!(await hasGoogleAccount(user.id))) {
    return c.json({ error: 'Google account not linked' }, 400);
  }

  if (!(await getUserPasswordHash(user.id))) {
    return c.json({ error: 'Cannot unlink Google without a password set' }, 400);
  }

  await (db as any)
    .delete(authAccounts)
    .where(
      and(
        eq((authAccounts as any).userId, user.id),
        eq((authAccounts as any).providerId, 'google')
      )
    );

  await (db as any)
    .update(users)
    .set({ updatedAt: getNow() })
    .where(eq((users as any).id, user.id));

  return c.json({ message: 'Google account unlinked' });
});

// ==================== Dashboard Summary Route ====================

// Get dashboard summary (welcome panel data)
dashboard.get('/summary', async (c) => {
  const user = (c as any).get('user') as AuthUser;
  const now = new Date();
  
  // Get membership duration
  const createdDate = new Date(user.createdAt);
  const membershipDays = Math.floor((now.getTime() - createdDate.getTime()) / (1000 * 60 * 60 * 24));
  
  // Get ticket count
  const userTickets = await dbAll<any>(
    (db as any)
      .select()
      .from(tickets)
      .where(eq((tickets as any).userId, user.id))
  );
  
  const totalTickets = userTickets.length;
  const confirmedTickets = userTickets.filter((t: any) => t.status === 'confirmed' || t.status === 'checked_in').length;
  const upcomingTickets = [];
  
  for (const ticket of userTickets) {
    if (ticket.status === 'cancelled') continue;
    
    const event = await dbGet<any>(
      (db as any)
        .select()
        .from(events)
        .where(eq((events as any).id, ticket.eventId))
    );
    
    if (event && new Date(event.startDatetime) > now) {
      upcomingTickets.push({ ticket, event });
    }
  }
  
  // Get pending payments count
  const ticketIds = userTickets.map((t: any) => t.id);
  let pendingPayments = 0;
  
  for (const ticketId of ticketIds) {
    const payment = await dbGet(
      (db as any)
        .select()
        .from(payments)
        .where(
          and(
            eq((payments as any).ticketId, ticketId),
            eq((payments as any).status, 'pending_approval')
          )
        )
    );
    
    if (payment) pendingPayments++;
  }
  
  return c.json({
    summary: {
      user: {
        name: user.name,
        email: user.email,
        accountStatus: user.accountStatus,
        memberSince: user.createdAt,
        membershipDays,
      },
      stats: {
        totalTickets,
        confirmedTickets,
        upcomingEvents: upcomingTickets.length,
        pendingPayments,
      },
    },
  });
});

export default dashboard;
