import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, In, Repository } from 'typeorm';
import { Progress } from './progress.entity';

export interface ProgressSummary {
  sections: { entityId: number; total: number; completed: number; percentage: number }[];
  modules: { entityId: number; total: number; completed: number; percentage: number }[];
  totals: { activitiesCompleted: number; modulesCompleted: number };
  lastVisited: { entityType: string; entityId: number; at: Date } | null;
}

@Injectable()
export class ProgressService {
  constructor(
    @InjectRepository(Progress)
    private readonly progressRepo: Repository<Progress>,
    private readonly dataSource: DataSource,
  ) {}

  async upsertProgress(
    sessionId: string,
    entityType: string,
    entityId: number,
    progressData: Record<string, any>,
  ): Promise<Progress> {
    const existing = await this.progressRepo.findOne({
      where: { anonymousSessionId: sessionId, entityType, entityId },
    });

    if (existing) {
      // Merge so a partial update (e.g. time spent) does not drop completion.
      existing.progressJson = { ...(existing.progressJson || {}), ...progressData };
      return this.progressRepo.save(existing);
    }

    return this.progressRepo.save(
      this.progressRepo.create({
        anonymousSessionId: sessionId,
        entityType,
        entityId,
        progressJson: progressData,
      }),
    );
  }

  /** Batch upsert used by the offline sync flow (section 22). */
  async syncBatch(
    sessionId: string,
    entries: { entityType: string; entityId: number; progressJson: Record<string, any> }[],
  ): Promise<number> {
    await this.dataSource.transaction(async () => {
      for (const entry of entries) {
        await this.upsertProgress(
          sessionId,
          entry.entityType,
          entry.entityId,
          entry.progressJson,
        );
      }
    });
    return entries.length;
  }

  getProgress(sessionId: string, entityType: string, entityId: number): Promise<Progress | null> {
    return this.progressRepo.findOne({
      where: { anonymousSessionId: sessionId, entityType, entityId },
    });
  }

  getSessionProgress(sessionId: string, since?: Date): Promise<Progress[]> {
    return this.progressRepo.find({
      where: { anonymousSessionId: sessionId },
      order: { lastUpdated: 'DESC' },
      ...(since ? {} : {}),
    });
  }

  getForEntities(
    sessionId: string,
    entityType: string,
    entityIds: number[],
  ): Promise<Progress[]> {
    if (!entityIds.length) return Promise.resolve([]);
    return this.progressRepo.find({
      where: { anonymousSessionId: sessionId, entityType, entityId: In(entityIds) },
    });
  }

  /**
   * Module progress = completed activities / total activities;
   * section progress = completed modules / total modules (section 23).
   */
  async summarise(sessionId: string): Promise<ProgressSummary> {
    const moduleRows = await this.dataSource.query(
      `SELECT m.id AS entityId,
              COUNT(DISTINCT a.id) AS total,
              COUNT(DISTINCT CASE
                WHEN JSON_EXTRACT(p.progress_json, '$.completed') = true THEN a.id
              END) AS completed
       FROM modules m
       JOIN lessons l ON l.module_id = m.id
       JOIN activities a ON a.lesson_id = l.id
       LEFT JOIN progress p
         ON p.entity_type = 'activity'
        AND p.entity_id = a.id
        AND p.anonymous_session_id = ?
       WHERE m.status = 'PUBLISHED'
       GROUP BY m.id`,
      [sessionId],
    );

    const sectionRows = await this.dataSource.query(
      `SELECT s.id AS entityId,
              COUNT(DISTINCT m.id) AS total,
              COUNT(DISTINCT CASE
                WHEN JSON_EXTRACT(p.progress_json, '$.completed') = true THEN m.id
              END) AS completed
       FROM sections s
       JOIN modules m ON m.section_id = s.id
       LEFT JOIN progress p
         ON p.entity_type = 'module'
        AND p.entity_id = m.id
        AND p.anonymous_session_id = ?
       WHERE s.status = 'PUBLISHED'
       GROUP BY s.id`,
      [sessionId],
    );

    const withPercentage = (rows: any[]) =>
      rows.map((row) => {
        const total = Number(row.total) || 0;
        const completed = Number(row.completed) || 0;
        return {
          entityId: Number(row.entityId),
          total,
          completed,
          percentage: total > 0 ? Math.round((completed / total) * 100) : 0,
        };
      });

    const latest = await this.progressRepo.findOne({
      where: { anonymousSessionId: sessionId },
      order: { lastUpdated: 'DESC' },
    });

    const modules = withPercentage(moduleRows);
    const sections = withPercentage(sectionRows);

    return {
      sections,
      modules,
      totals: {
        activitiesCompleted: modules.reduce((sum, m) => sum + m.completed, 0),
        modulesCompleted: modules.filter((m) => m.total > 0 && m.completed === m.total).length,
      },
      lastVisited: latest
        ? {
            entityType: latest.entityType,
            entityId: Number(latest.entityId),
            at: latest.lastUpdated,
          }
        : null,
    };
  }
}
