// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only

part of '../sqlite_repositories.dart';

/// SQLite implementation of the app-layer project statistics repository.
final class _SqliteApplicationProjectStatisticsRepository
    implements core.ProjectStatisticsRepository {
  /// Creates a project statistics repository for [db].
  _SqliteApplicationProjectStatisticsRepository(
    this.db, {
    required this.defaultOwnerId,
  });

  /// Shared SQLite database handle.
  final SchedulerDb db;

  /// Owner used for save methods without an owner parameter.
  final String defaultOwnerId;

  /// Returns statistics by project id.
  @override
  Future<core.ProjectStatistics?> findByProjectId(String projectId) async {
    final row = await _statisticsByProjectId(projectId);
    return row == null ? null : _projectStatisticsFromRow(row);
  }

  /// Returns statistics plus owner/revision metadata.
  @override
  Future<core.RepositoryRecord<core.ProjectStatistics>?> findRecordByProjectId(
      String projectId) async {
    final row = await _statisticsByProjectId(projectId);
    if (row == null) {
      return null;
    }
    return core.RepositoryRecord(
      value: _projectStatisticsFromRow(row),
      ownerId: row.ownerId,
      revision: row.revision,
    );
  }

  /// Returns every project statistics aggregate.
  @override
  Future<List<core.ProjectStatistics>> findAll() async {
    final rows = await (db.select(db.projectStatisticsTable)
          ..orderBy([(table) => drift.OrderingTerm.asc(table.projectId)]))
        .get();
    return List<core.ProjectStatistics>.unmodifiable(
      rows.map(_projectStatisticsFromRow),
    );
  }

  /// Saves [statistics], inserting it when absent.
  @override
  Future<void> save(core.ProjectStatistics statistics) async {
    final row = await _statisticsByProjectId(statistics.projectId);
    if (row == null) {
      await db.into(db.projectStatisticsTable).insert(
            _projectStatisticsCompanion(
              statistics,
              ownerId: defaultOwnerId,
              revision: core.Revision.initial.value,
              createdAt: _epoch,
              updatedAt: _epoch,
            ),
          );
      return;
    }
    await (db.update(db.projectStatisticsTable)
          ..where((table) => table.projectId.equals(statistics.projectId)))
        .write(
      _projectStatisticsCompanion(
        statistics,
        ownerId: row.ownerId,
        revision: core.Revision(row.revision).next().value,
        createdAt: row.createdAtUtc,
        updatedAt: _epoch,
      ),
    );
  }

  /// Saves [statistics] only when the repository revision matches [expectedRevision].
  @override
  Future<core.RepositoryMutationResult> saveIfRevision({
    required core.ProjectStatistics statistics,
    required String ownerId,
    required int expectedRevision,
  }) async {
    final row = await _statisticsByProjectId(statistics.projectId);
    final failure = _compareForWrite(
      row: row,
      ownerId: ownerId,
      expectedRevision: expectedRevision,
      entityId: statistics.projectId,
      rowOwnerId: row?.ownerId,
      rowRevision: row?.revision,
    );
    if (failure != null) {
      return failure;
    }
    final nextRevision = core.Revision(expectedRevision).next().value;
    await (db.update(db.projectStatisticsTable)
          ..where(
            (table) =>
                table.projectId.equals(statistics.projectId) &
                table.ownerId.equals(ownerId) &
                table.revision.equals(expectedRevision),
          ))
        .write(
      _projectStatisticsCompanion(
        statistics,
        ownerId: ownerId,
        revision: nextRevision,
        createdAt: row!.createdAtUtc,
        updatedAt: _epoch,
      ),
    );
    return core.RepositoryMutationResult.success(revision: nextRevision);
  }

  /// Returns a raw project statistics row by project id.
  Future<ProjectStatisticsRow?> _statisticsByProjectId(String projectId) {
    return (db.select(db.projectStatisticsTable)
          ..where((table) => table.projectId.equals(projectId)))
        .getSingleOrNull();
  }
}

/// Builds a Drift companion for [statistics].
ProjectStatisticsTableCompanion _projectStatisticsCompanion(
  core.ProjectStatistics statistics, {
  required String ownerId,
  required int revision,
  required DateTime createdAt,
  required DateTime updatedAt,
}) {
  return ProjectStatisticsTableCompanion.insert(
    projectId: statistics.projectId,
    ownerId: ownerId,
    completedTaskCount: statistics.completedTaskCount,
    durationMinuteCountsJson: _intCountMapJson(
      statistics.durationMinuteCounts,
    ),
    completionTimeBucketCountsJson: _enumCountMapJson(
      statistics.completionTimeBucketCounts,
    ),
    totalPushesBeforeCompletion: statistics.totalPushesBeforeCompletion,
    completedAfterPushCount: statistics.completedAfterPushCount,
    rewardCountsJson: _enumCountMapJson(statistics.rewardCounts),
    difficultyCountsJson: _enumCountMapJson(statistics.difficultyCounts),
    reminderProfileCountsJson: _enumCountMapJson(
      statistics.reminderProfileCounts,
    ),
    revision: revision,
    createdAtUtc: createdAt.toUtc(),
    updatedAtUtc: updatedAt.toUtc(),
  );
}

/// Converts a project statistics row to a core aggregate.
core.ProjectStatistics _projectStatisticsFromRow(ProjectStatisticsRow row) {
  return core.ProjectStatistics(
    projectId: row.projectId,
    completedTaskCount: row.completedTaskCount,
    durationMinuteCounts: _intCountMap(row.durationMinuteCountsJson),
    completionTimeBucketCounts: _enumCountMap(
      row.completionTimeBucketCountsJson,
      core.ProjectCompletionTimeBucket.values,
    ),
    totalPushesBeforeCompletion: row.totalPushesBeforeCompletion,
    completedAfterPushCount: row.completedAfterPushCount,
    rewardCounts: _enumCountMap(row.rewardCountsJson, core.RewardLevel.values),
    difficultyCounts: _enumCountMap(
      row.difficultyCountsJson,
      core.DifficultyLevel.values,
    ),
    reminderProfileCounts: _enumCountMap(
      row.reminderProfileCountsJson,
      core.ReminderProfile.values,
    ),
  );
}
