<?php

namespace MintyPHP\Module\Audit\Repository;

use MintyPHP\DB;
use MintyPHP\Module\Audit\Domain\UserLifecycleAction;
use MintyPHP\Module\Audit\Domain\UserLifecycleStatus;
use MintyPHP\Module\Audit\Domain\UserLifecycleTriggerType;
use MintyPHP\Repository\Support\RepoQuery;

/** Records user lifecycle transitions (deactivation, deletion, restore) with reason codes and snapshots. */
class UserLifecycleAuditRepository
{
    private const FILTER_OPTIONS_LIMIT_MAX = 200;

    public function create(array $row): int|false
    {
        $id = DB::insert(
            'insert into user_lifecycle_audit_log (
                run_uuid, action, trigger_type, status, reason_code,
                policy_deactivate_days, policy_delete_days,
                actor_user_id, target_user_id, target_user_uuid, target_user_email,
                snapshot_enc, snapshot_version, created_at
            ) values (?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())',
            (string) ($row['run_uuid'] ?? ''),
            (string) ($row['action'] ?? ''),
            (string) ($row['trigger_type'] ?? ''),
            (string) ($row['status'] ?? ''),
            $row['reason_code'] ?? null,
            (string) ((int) ($row['policy_deactivate_days'] ?? 0)),
            (string) ((int) ($row['policy_delete_days'] ?? 0)),
            $row['actor_user_id'] !== null ? (string) ((int) $row['actor_user_id']) : null,
            $row['target_user_id'] !== null ? (string) ((int) $row['target_user_id']) : null,
            $row['target_user_uuid'] ?? null,
            $row['target_user_email'] ?? null,
            $row['snapshot_enc'] ?? null,
            (string) ((int) ($row['snapshot_version'] ?? 1))
        );
        return $id ? (int) $id : false;
    }

    public function updateStatus(int $id, string $status, ?string $reasonCode = null): bool
    {
        if ($id <= 0) {
            return false;
        }
        $normalizedStatus = UserLifecycleStatus::tryNormalize($status);
        if ($normalizedStatus === null) {
            return false;
        }

        $updated = DB::update(
            'update user_lifecycle_audit_log set status = ?, reason_code = ? where id = ?',
            $normalizedStatus->value,
            $reasonCode,
            (string) $id
        );
        return $updated !== false;
    }

    public function listPaged(array $filters): array
    {
        $search = trim((string) ($filters['search'] ?? ''));
        $actions = RepoQuery::normalizeStringList(
            $filters['actions'] ?? '',
            self::FILTER_OPTIONS_LIMIT_MAX,
            static fn (string $value): string => UserLifecycleAction::tryNormalize($value)->value ?? ''
        );
        $statuses = RepoQuery::normalizeStringList(
            $filters['statuses'] ?? '',
            self::FILTER_OPTIONS_LIMIT_MAX,
            static fn (string $value): string => UserLifecycleStatus::tryNormalize($value)->value ?? ''
        );
        $triggerTypes = RepoQuery::normalizeStringList(
            $filters['trigger_types'] ?? '',
            self::FILTER_OPTIONS_LIMIT_MAX,
            static fn (string $value): string => UserLifecycleTriggerType::tryNormalize($value)->value ?? ''
        );
        $actorUserIds = array_slice(
            RepoQuery::normalizeIdList($filters['actor_user_ids'] ?? ''),
            0,
            self::FILTER_OPTIONS_LIMIT_MAX
        );
        $createdFrom = trim((string) ($filters['created_from'] ?? ''));
        $createdTo = trim((string) ($filters['created_to'] ?? ''));

        [$limit, $offset] = RepoQuery::sanitizeLimitOffset($filters, 20, 1, 200, 0);
        [$order, $dir] = RepoQuery::sanitizeOrder(
            $filters,
            ['id', 'created_at', 'action', 'trigger_type', 'status'],
            'created_at',
            'desc'
        );

        $where = [];
        $params = [];
        RepoQuery::addLikeFilter(
            $where,
            $params,
            [
                'user_lifecycle_audit_log.run_uuid',
                'user_lifecycle_audit_log.target_user_uuid',
                'user_lifecycle_audit_log.target_user_email',
                'user_lifecycle_audit_log.reason_code',
            ],
            $search
        );
        if ($actions !== []) {
            $where[] = 'user_lifecycle_audit_log.action in (???)';
            $params[] = $actions;
        }
        if ($statuses !== []) {
            $where[] = 'user_lifecycle_audit_log.status in (???)';
            $params[] = $statuses;
        }
        if ($triggerTypes !== []) {
            $where[] = 'user_lifecycle_audit_log.trigger_type in (???)';
            $params[] = $triggerTypes;
        }
        if ($actorUserIds !== []) {
            $where[] = 'user_lifecycle_audit_log.actor_user_id in (???)';
            $params[] = $actorUserIds;
        }
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdFrom)) {
            $where[] = 'user_lifecycle_audit_log.created_at >= ?';
            $params[] = $createdFrom . ' 00:00:00';
        }
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdTo)) {
            $where[] = 'user_lifecycle_audit_log.created_at <= ?';
            $params[] = $createdTo . ' 23:59:59';
        }

        $whereSql = $where ? (' where ' . implode(' and ', $where)) : '';
        $fromSql = ' from user_lifecycle_audit_log ' .
            'left join users actor_user on actor_user.id = user_lifecycle_audit_log.actor_user_id ' .
            'left join users restored_by_user on restored_by_user.id = user_lifecycle_audit_log.restored_by_user_id ' .
            'left join users restored_user on restored_user.id = user_lifecycle_audit_log.restored_user_id ';

        $total = (int) (DB::selectValue('select count(*)' . $fromSql . $whereSql, ...$params) ?? 0);

        $rows = DB::select(
            'select
                user_lifecycle_audit_log.id,
                user_lifecycle_audit_log.run_uuid,
                user_lifecycle_audit_log.action,
                user_lifecycle_audit_log.trigger_type,
                user_lifecycle_audit_log.status,
                user_lifecycle_audit_log.reason_code,
                user_lifecycle_audit_log.policy_deactivate_days,
                user_lifecycle_audit_log.policy_delete_days,
                user_lifecycle_audit_log.actor_user_id,
                user_lifecycle_audit_log.target_user_id,
                user_lifecycle_audit_log.target_user_uuid,
                user_lifecycle_audit_log.target_user_email,
                user_lifecycle_audit_log.snapshot_version,
                user_lifecycle_audit_log.restored_at,
                user_lifecycle_audit_log.restored_by_user_id,
                user_lifecycle_audit_log.restored_user_id,
                user_lifecycle_audit_log.created_at,
                actor_user.uuid,
                actor_user.display_name,
                actor_user.email,
                restored_by_user.uuid,
                restored_by_user.display_name,
                restored_by_user.email,
                restored_user.uuid,
                restored_user.display_name,
                restored_user.email
            ' . $fromSql . $whereSql .
            sprintf(' order by user_lifecycle_audit_log.`%s` %s limit ? offset ?', $order, $dir),
            ...array_merge($params, [(string) $limit, (string) $offset])
        );

        $normalized = [];
        if (is_array($rows)) {
            foreach ($rows as $row) {
                $item = $this->normalizeRow($row, false);
                if ($item !== null) {
                    $normalized[] = $item;
                }
            }
        }

        return ['total' => $total, 'rows' => $normalized];
    }

    public function find(int $id): ?array
    {
        if ($id <= 0) {
            return null;
        }

        $row = DB::selectOne(
            'select
                user_lifecycle_audit_log.id,
                user_lifecycle_audit_log.run_uuid,
                user_lifecycle_audit_log.action,
                user_lifecycle_audit_log.trigger_type,
                user_lifecycle_audit_log.status,
                user_lifecycle_audit_log.reason_code,
                user_lifecycle_audit_log.policy_deactivate_days,
                user_lifecycle_audit_log.policy_delete_days,
                user_lifecycle_audit_log.actor_user_id,
                user_lifecycle_audit_log.target_user_id,
                user_lifecycle_audit_log.target_user_uuid,
                user_lifecycle_audit_log.target_user_email,
                user_lifecycle_audit_log.snapshot_enc,
                user_lifecycle_audit_log.snapshot_version,
                user_lifecycle_audit_log.restored_at,
                user_lifecycle_audit_log.restored_by_user_id,
                user_lifecycle_audit_log.restored_user_id,
                user_lifecycle_audit_log.created_at,
                actor_user.uuid,
                actor_user.display_name,
                actor_user.email,
                restored_by_user.uuid,
                restored_by_user.display_name,
                restored_by_user.email,
                restored_user.uuid,
                restored_user.display_name,
                restored_user.email
            from user_lifecycle_audit_log
            left join users actor_user on actor_user.id = user_lifecycle_audit_log.actor_user_id
            left join users restored_by_user on restored_by_user.id = user_lifecycle_audit_log.restored_by_user_id
            left join users restored_user on restored_user.id = user_lifecycle_audit_log.restored_user_id
            where user_lifecycle_audit_log.id = ?
            limit 1',
            (string) $id
        );

        return $this->normalizeRow($row, true);
    }

    public function findDeleteEventForRestore(int $id, bool $forUpdate = false): ?array
    {
        if ($id <= 0) {
            return null;
        }

        $query = 'select
                id, run_uuid, action, trigger_type, status, reason_code,
                policy_deactivate_days, policy_delete_days, actor_user_id,
                target_user_id, target_user_uuid, target_user_email,
                snapshot_enc, snapshot_version, restored_at,
                restored_by_user_id, restored_user_id, created_at
            from user_lifecycle_audit_log
            where id = ?
              and action = \'delete\'
              and status = \'success\'
            limit 1';
        if ($forUpdate) {
            $query .= ' for update';
        }

        $row = DB::selectOne($query, (string) $id);
        if (!is_array($row)) {
            return null;
        }
        $item = $row['user_lifecycle_audit_log'] ?? $row;
        return is_array($item) ? $item : null;
    }

    public function markRestored(int $id, int $restoredBy, int $restoredUserId): bool
    {
        if ($id <= 0 || $restoredBy <= 0 || $restoredUserId <= 0) {
            return false;
        }
        $updated = DB::update(
            'update user_lifecycle_audit_log
             set restored_at = NOW(),
                 restored_by_user_id = ?,
                 restored_user_id = ?
             where id = ? and restored_at is null',
            (string) $restoredBy,
            (string) $restoredUserId,
            (string) $id
        );
        return (int) $updated > 0;
    }

    /**
     * Count audit-log events for a single action+status within the last $days days (UTC).
     */
    public function countActionInWindow(string $action, int $days, string $status): int
    {
        if ($days <= 0) {
            return 0;
        }
        $value = DB::selectValue(
            'select count(*)
             from user_lifecycle_audit_log
             where action = ?
               and status = ?
               and created_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL ? DAY)',
            $action,
            $status,
            (string) $days
        );
        return (int) ($value ?? 0);
    }

    /**
     * Map of action → count for the given status within the last $days days (UTC).
     *
     * @return array<string, int>
     */
    public function sumByActionInWindow(int $days, string $status): array
    {
        if ($days <= 0) {
            return [];
        }
        $rows = DB::select(
            'select action, count(*) as cnt
             from user_lifecycle_audit_log
             where status = ?
               and created_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL ? DAY)
             group by action',
            $status,
            (string) $days
        );
        $summary = [];
        if (is_array($rows)) {
            foreach ($rows as $row) {
                $item = is_array($row) ? ($row['user_lifecycle_audit_log'] ?? $row) : null;
                if (!is_array($item)) {
                    continue;
                }
                $action = trim((string) ($item['action'] ?? ''));
                if ($action === '') {
                    continue;
                }
                $summary[$action] = (int) ($item['cnt'] ?? 0);
            }
        }
        return $summary;
    }

    public function purgeOlderThanDays(int $days): int
    {
        if ($days <= 0) {
            return 0;
        }

        $cutoff = (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))
            ->modify('-' . $days . ' days')
            ->format('Y-m-d H:i:s');
        $deleted = DB::delete('delete from user_lifecycle_audit_log where created_at < ?', $cutoff);
        return is_int($deleted) ? $deleted : 0;
    }

    private function normalizeRow(mixed $row, bool $includeSnapshot): ?array
    {
        if (!is_array($row)) {
            return null;
        }
        $item = $row['user_lifecycle_audit_log'] ?? [];
        if (!is_array($item) || !isset($item['id'])) {
            return null;
        }

        $actor = is_array($row['actor_user'] ?? null) ? $row['actor_user'] : [];
        $restoredBy = is_array($row['restored_by_user'] ?? null) ? $row['restored_by_user'] : [];
        $restoredUser = is_array($row['restored_user'] ?? null) ? $row['restored_user'] : [];

        $item['actor_user_uuid'] = (string) ($actor['uuid'] ?? '');
        $item['actor_user_display_name'] = (string) ($actor['display_name'] ?? '');
        $item['actor_user_email'] = (string) ($actor['email'] ?? '');
        $item['restored_by_user_uuid'] = (string) ($restoredBy['uuid'] ?? '');
        $item['restored_by_user_display_name'] = (string) ($restoredBy['display_name'] ?? '');
        $item['restored_by_user_email'] = (string) ($restoredBy['email'] ?? '');
        $item['restored_user_uuid'] = (string) ($restoredUser['uuid'] ?? '');
        $item['restored_user_display_name'] = (string) ($restoredUser['display_name'] ?? '');
        $item['restored_user_email'] = (string) ($restoredUser['email'] ?? '');

        if (!$includeSnapshot) {
            unset($item['snapshot_enc']);
        }
        return $item;
    }

}
