import { prisma } from '../../config/database';
import { logger } from '../../utils/logger';

// ── Raw query result types ──────────────────────────────────────────────

type CountRow = { count: bigint };
type ModuleCountRow = { module: string; views: bigint };
type DayViewRow = { day: Date; views: bigint; module: string };
type CountryRow = { country: string; views: bigint };
type CityRow = { city: string; region: string | null; country: string; views: bigint };
type GeoPointRow = { latitude: number; longitude: number; count: bigint; module: string };
type ContentRow = { id: string; title: string; views: bigint; module: string };
type UserEngagementRow = {
  user_id: string;
  name: string | null;
  email: string;
  video_views: bigint;
  photo_views: bigint;
  total_watch_time: bigint;
  last_active: Date | null;
};

// ── Helpers ─────────────────────────────────────────────────────────────

function sinceDate(days: number): Date {
  const d = new Date();
  d.setDate(d.getDate() - days);
  return d;
}

function toNum(v: bigint | null | undefined): number {
  return Number(v ?? 0);
}

// ── Service ─────────────────────────────────────────────────────────────

export const analyticsService = {
  /**
   * Cross-module overview: total views, unique sessions, views by day, module breakdown
   */
  async getOverview(days: number) {
    const since = sinceDate(days);

    const [docViews, videoViews, photoViews, docSessions, viewsByDay] = await Promise.all([
      prisma.docsPageView.count({ where: { createdAt: { gte: since } } }),
      prisma.videoView.count({ where: { createdAt: { gte: since } } }),
      prisma.photoView.count({ where: { viewedAt: { gte: since } } }),

      prisma.$queryRaw<CountRow[]>`
        SELECT COUNT(DISTINCT "sessionHash") as count
        FROM docs_page_views
        WHERE "createdAt" >= ${since} AND "sessionHash" IS NOT NULL
      `,

      // Combined views by day from all three modules
      prisma.$queryRaw<DayViewRow[]>`
        SELECT day, SUM(views)::bigint as views, module FROM (
          SELECT DATE("createdAt") as day, COUNT(*) as views, 'docs' as module
          FROM docs_page_views WHERE "createdAt" >= ${since} GROUP BY DATE("createdAt")
          UNION ALL
          SELECT DATE(created_at) as day, COUNT(*) as views, 'video' as module
          FROM video_views WHERE created_at >= ${since} GROUP BY DATE(created_at)
          UNION ALL
          SELECT DATE(viewed_at) as day, COUNT(*) as views, 'photo' as module
          FROM photo_views WHERE viewed_at >= ${since} GROUP BY DATE(viewed_at)
        ) combined
        GROUP BY day, module
        ORDER BY day ASC
      `,
    ]);

    const totalViews = docViews + videoViews + photoViews;
    const uniqueSessions = toNum(docSessions[0]?.count);

    return {
      totalViews,
      uniqueSessions,
      avgViewsPerDay: days > 0 ? Math.round(totalViews / days) : 0,
      moduleBreakdown: [
        { module: 'docs', views: docViews },
        { module: 'video', views: videoViews },
        { module: 'photo', views: photoViews },
      ],
      viewsByDay: viewsByDay.map((r) => ({
        date: r.day instanceof Date ? r.day.toISOString().split('T')[0] : String(r.day),
        views: toNum(r.views),
        module: r.module,
      })),
    };
  },

  /**
   * Geographic analytics: views by country, top cities, map points
   */
  async getGeoAnalytics(days: number) {
    const since = sinceDate(days);

    const [viewsByCountry, topCities, geoPoints] = await Promise.all([
      prisma.$queryRaw<CountryRow[]>`
        SELECT country, SUM(views)::bigint as views FROM (
          SELECT country, COUNT(*) as views FROM docs_page_views
          WHERE "createdAt" >= ${since} AND country IS NOT NULL GROUP BY country
          UNION ALL
          SELECT country, COUNT(*) as views FROM video_views
          WHERE created_at >= ${since} AND country IS NOT NULL GROUP BY country
          UNION ALL
          SELECT country, COUNT(*) as views FROM photo_views
          WHERE viewed_at >= ${since} AND country IS NOT NULL GROUP BY country
        ) combined
        GROUP BY country ORDER BY views DESC LIMIT 30
      `,

      prisma.$queryRaw<CityRow[]>`
        SELECT city, region, country, SUM(views)::bigint as views FROM (
          SELECT city, region, country, COUNT(*) as views FROM docs_page_views
          WHERE "createdAt" >= ${since} AND city IS NOT NULL GROUP BY city, region, country
          UNION ALL
          SELECT city, region, country, COUNT(*) as views FROM video_views
          WHERE created_at >= ${since} AND city IS NOT NULL GROUP BY city, region, country
          UNION ALL
          SELECT city, region, country, COUNT(*) as views FROM photo_views
          WHERE viewed_at >= ${since} AND city IS NOT NULL GROUP BY city, region, country
        ) combined
        GROUP BY city, region, country ORDER BY views DESC LIMIT 30
      `,

      prisma.$queryRaw<GeoPointRow[]>`
        SELECT latitude, longitude, SUM(count)::bigint as count, module FROM (
          SELECT latitude, longitude, COUNT(*) as count, 'docs' as module FROM docs_page_views
          WHERE "createdAt" >= ${since} AND latitude IS NOT NULL GROUP BY latitude, longitude
          UNION ALL
          SELECT latitude, longitude, COUNT(*) as count, 'video' as module FROM video_views
          WHERE created_at >= ${since} AND latitude IS NOT NULL GROUP BY latitude, longitude
          UNION ALL
          SELECT latitude, longitude, COUNT(*) as count, 'photo' as module FROM photo_views
          WHERE viewed_at >= ${since} AND latitude IS NOT NULL GROUP BY latitude, longitude
        ) combined
        GROUP BY latitude, longitude, module
        ORDER BY count DESC LIMIT 500
      `,
    ]);

    return {
      viewsByCountry: viewsByCountry.map((r) => ({
        country: r.country,
        views: toNum(r.views),
      })),
      topCities: topCities.map((r) => ({
        city: r.city,
        region: r.region,
        country: r.country,
        views: toNum(r.views),
      })),
      geoPoints: geoPoints.map((r) => ({
        latitude: r.latitude,
        longitude: r.longitude,
        count: toNum(r.count),
        module: r.module,
      })),
    };
  },

  /**
   * Content analytics: top pages/videos/photos by views
   */
  async getContentAnalytics(days: number, module: string) {
    const since = sinceDate(days);
    const results: { id: string; title: string; views: number; module: string }[] = [];

    if (module === 'all' || module === 'docs') {
      const docs = await prisma.$queryRaw<ContentRow[]>`
        SELECT path as id, path as title, COUNT(*)::bigint as views, 'docs' as module
        FROM docs_page_views
        WHERE "createdAt" >= ${since}
        GROUP BY path
        ORDER BY views DESC
        LIMIT 20
      `;
      results.push(...docs.map((r) => ({ id: r.id, title: r.title, views: toNum(r.views), module: 'docs' })));
    }

    if (module === 'all' || module === 'video') {
      const videos = await prisma.$queryRaw<{ id: number; title: string; views: bigint }[]>`
        SELECT v.id, v.title, COUNT(vv.id)::bigint as views
        FROM videos v
        LEFT JOIN video_views vv ON vv.video_id = v.id AND vv.created_at >= ${since}
        GROUP BY v.id, v.title
        HAVING COUNT(vv.id) > 0
        ORDER BY views DESC
        LIMIT 20
      `;
      results.push(...videos.map((r) => ({ id: String(r.id), title: r.title, views: toNum(r.views), module: 'video' })));
    }

    if (module === 'all' || module === 'photo') {
      const photos = await prisma.$queryRaw<{ id: number; title: string; views: bigint }[]>`
        SELECT p.id, COALESCE(p.title, p.filename) as title, COUNT(pv.id)::bigint as views
        FROM photos p
        LEFT JOIN photo_views pv ON pv.photo_id = p.id AND pv.viewed_at >= ${since}
        GROUP BY p.id, p.title, p.filename
        HAVING COUNT(pv.id) > 0
        ORDER BY views DESC
        LIMIT 20
      `;
      results.push(...photos.map((r) => ({ id: String(r.id), title: r.title, views: toNum(r.views), module: 'photo' })));
    }

    // Sort combined results by views descending
    results.sort((a, b) => b.views - a.views);

    return { content: results.slice(0, 30) };
  },

  /**
   * User engagement list (paginated)
   */
  async getUsersEngagement(days: number, page: number, limit: number) {
    const since = sinceDate(days);
    const offset = (page - 1) * limit;

    const [users, totalResult] = await Promise.all([
      prisma.$queryRaw<UserEngagementRow[]>`
        SELECT
          u.id as user_id,
          u.name,
          u.email,
          COALESCE(vv.cnt, 0)::bigint as video_views,
          COALESCE(pv.cnt, 0)::bigint as photo_views,
          COALESCE(vv.watch_time, 0)::bigint as total_watch_time,
          GREATEST(vv.last_active, pv.last_active) as last_active
        FROM users u
        LEFT JOIN (
          SELECT user_id, COUNT(*) as cnt,
                 SUM(watch_time_seconds) as watch_time,
                 MAX(created_at) as last_active
          FROM video_views WHERE created_at >= ${since} AND user_id IS NOT NULL
          GROUP BY user_id
        ) vv ON vv.user_id = u.id
        LEFT JOIN (
          SELECT user_id, COUNT(*) as cnt,
                 MAX(viewed_at) as last_active
          FROM photo_views WHERE viewed_at >= ${since} AND user_id IS NOT NULL
          GROUP BY user_id
        ) pv ON pv.user_id = u.id
        WHERE COALESCE(vv.cnt, 0) + COALESCE(pv.cnt, 0) > 0
        ORDER BY (COALESCE(vv.cnt, 0) + COALESCE(pv.cnt, 0)) DESC
        LIMIT ${limit} OFFSET ${offset}
      `,

      prisma.$queryRaw<CountRow[]>`
        SELECT COUNT(DISTINCT u.id)::bigint as count
        FROM users u
        LEFT JOIN video_views vv ON vv.user_id = u.id AND vv.created_at >= ${since}
        LEFT JOIN photo_views pv ON pv.user_id = u.id AND pv.viewed_at >= ${since}
        WHERE vv.id IS NOT NULL OR pv.id IS NOT NULL
      `,
    ]);

    return {
      users: users.map((r) => ({
        userId: r.user_id,
        name: r.name,
        email: r.email,
        videoViews: toNum(r.video_views),
        photoViews: toNum(r.photo_views),
        totalWatchTime: toNum(r.total_watch_time),
        lastActive: r.last_active ? r.last_active.toISOString() : null,
      })),
      pagination: {
        page,
        limit,
        total: toNum(totalResult[0]?.count),
        totalPages: Math.ceil(toNum(totalResult[0]?.count) / limit),
      },
    };
  },

  /**
   * Individual user activity detail
   */
  async getUserActivity(userId: string, days: number) {
    const since = sinceDate(days);

    const [videoViews, photoViews, videoStats, photoStats] = await Promise.all([
      prisma.videoView.findMany({
        where: { userId, createdAt: { gte: since } },
        include: { video: { select: { id: true, title: true } } },
        orderBy: { createdAt: 'desc' },
        take: 50,
      }),

      prisma.photoView.findMany({
        where: { userId, viewedAt: { gte: since } },
        include: { photo: { select: { id: true, title: true, filename: true } } },
        orderBy: { viewedAt: 'desc' },
        take: 50,
      }),

      prisma.videoView.aggregate({
        where: { userId, createdAt: { gte: since } },
        _count: true,
        _sum: { watchTimeSeconds: true },
      }),

      prisma.photoView.aggregate({
        where: { userId, viewedAt: { gte: since } },
        _count: true,
      }),
    ]);

    return {
      summary: {
        videoViews: videoStats._count,
        totalWatchTime: videoStats._sum.watchTimeSeconds ?? 0,
        photoViews: photoStats._count,
      },
      recentVideoViews: videoViews.map((v) => ({
        videoId: v.videoId,
        videoTitle: v.video?.title ?? 'Untitled',
        watchTime: v.watchTimeSeconds,
        completed: v.completed,
        viewedAt: v.createdAt.toISOString(),
      })),
      recentPhotoViews: photoViews.map((v) => ({
        photoId: v.photoId,
        photoTitle: v.photo?.title ?? v.photo?.filename ?? 'Untitled',
        viewedAt: v.viewedAt.toISOString(),
      })),
    };
  },

  /**
   * Self-service: authenticated user's own activity
   */
  async getMyActivity(userId: string, days: number) {
    return this.getUserActivity(userId, days);
  },
};
