<?php
// app/services/LogService.php

declare(strict_types=1);

namespace ScMedia\Services;

use ScMedia\Db\Db;

final class LogService {
  private Db $db;
  private SettingsService $settings;

  public function __construct(Db $db, SettingsService $settings) {
    $this->db = $db;
    $this->settings = $settings;
  }

  public function log(string $level, string $message, ?array $context = null): void {
    $level = in_array($level, ['info','warn','error','debug'], true) ? $level : 'info';
    if (!$this->shouldLog($level)) return;
    $ctxJson = $context ? json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null;
    $this->db->exec("
      INSERT INTO app_logs (level, message, context_json)
      VALUES (:level, :msg, :ctx)
    ", [
      ':level' => $level,
      ':msg' => $message,
      ':ctx' => $ctxJson,
    ]);
  }

  public function listByDate(string $date, int $limit = 5000): array {
    $date = trim($date);
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
      return [];
    }
    $limit = max(1, min((int)$limit, 5000));

    $sql = "
      SELECT id, ts, level, message, context_json
      FROM app_logs
      WHERE ts >= :d AND ts < DATE_ADD(:d, INTERVAL 1 DAY)
      ORDER BY id ASC
      LIMIT {$limit}
    ";
    return $this->db->fetchAll($sql, [
      ':d' => $date . ' 00:00:00',
    ]);
  }

  public function listByRange(string $from, string $to, int $limit = 5000): array {
    $from = trim($from);
    $to = trim($to);
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $from) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $to)) {
      return [];
    }
    $limit = max(1, min((int)$limit, 5000));

    $sql = "
      SELECT id, ts, level, message, context_json
      FROM app_logs
      WHERE ts >= :from AND ts < DATE_ADD(:to, INTERVAL 1 DAY)
      ORDER BY id ASC
      LIMIT {$limit}
    ";
    return $this->db->fetchAll($sql, [
      ':from' => $from . ' 00:00:00',
      ':to' => $to . ' 00:00:00',
    ]);
  }

  public function listByDatePaged(string $date, int $page = 1, int $perPage = 100, ?string $level = null, ?string $sort = null, ?string $dir = null): array {
    $date = trim($date);
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
      return ['items' => [], 'total' => 0, 'page' => 1, 'per_page' => 0];
    }
    $per = max(1, min((int)$perPage, 500));
    $p = max(1, (int)$page);
    $offset = ($p - 1) * $per;

    [$whereSql, $params] = $this->buildLogsFilter($date, $date, $level);

    $countRow = $this->db->fetchOne("SELECT COUNT(*) AS cnt FROM app_logs {$whereSql}", $params) ?? [];
    $total = (int)($countRow['cnt'] ?? 0);

    $orderBy = $this->buildLogsOrder($sort, $dir);
    $sql = "
      SELECT id, ts, level, message, context_json
      FROM app_logs
      {$whereSql}
      ORDER BY {$orderBy}
      LIMIT {$per} OFFSET {$offset}
    ";
    $items = $this->db->fetchAll($sql, $params);

    return ['items' => $items, 'total' => $total, 'page' => $p, 'per_page' => $per];
  }

  public function listByRangePaged(string $from, string $to, int $page = 1, int $perPage = 100, ?string $level = null, ?string $sort = null, ?string $dir = null): array {
    $from = trim($from);
    $to = trim($to);
    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $from) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $to)) {
      return ['items' => [], 'total' => 0, 'page' => 1, 'per_page' => 0];
    }
    $per = max(1, min((int)$perPage, 500));
    $p = max(1, (int)$page);
    $offset = ($p - 1) * $per;

    [$whereSql, $params] = $this->buildLogsFilter($from, $to, $level);

    $countRow = $this->db->fetchOne("SELECT COUNT(*) AS cnt FROM app_logs {$whereSql}", $params) ?? [];
    $total = (int)($countRow['cnt'] ?? 0);

    $orderBy = $this->buildLogsOrder($sort, $dir);
    $sql = "
      SELECT id, ts, level, message, context_json
      FROM app_logs
      {$whereSql}
      ORDER BY {$orderBy}
      LIMIT {$per} OFFSET {$offset}
    ";
    $items = $this->db->fetchAll($sql, $params);

    return ['items' => $items, 'total' => $total, 'page' => $p, 'per_page' => $per];
  }

  public function cleanup(int $keepDays): int {
    if ($keepDays <= 0) {
      return $this->db->exec("DELETE FROM app_logs");
    }
    $days = (int)$keepDays;
    return $this->db->exec("
      DELETE FROM app_logs
      WHERE ts < DATE_SUB(NOW(), INTERVAL {$days} DAY)
    ");
  }

  private function shouldLog(string $level): bool {
    $order = ['debug' => 10, 'info' => 20, 'warn' => 30, 'error' => 40];
    $lvl = $order[$level] ?? 20;

    if ($level === 'debug') {
      $cfg = $this->settings->getConfig();
      $enabled = (bool)($cfg['app']['debug_logs_enabled'] ?? false);
      if (!$enabled) {
        return false;
      }
    }

    $all = $this->settings->getAll();
    $logs = $all['logs'] ?? [];
    $min = (string)($logs['level'] ?? 'info');
    $minVal = $order[$min] ?? 20;

    return $lvl >= $minVal;
  }

  private function buildLogsFilter(string $from, string $to, ?string $level): array {
    $params = [
      ':from' => $from . ' 00:00:00',
      ':to' => $to . ' 00:00:00',
    ];
    $sql = "WHERE ts >= :from AND ts < DATE_ADD(:to, INTERVAL 1 DAY)";
    $allowed = ['debug', 'info', 'warn', 'error'];
    if ($level && in_array($level, $allowed, true)) {
      $sql .= " AND level = :level";
      $params[':level'] = $level;
    }
    return [$sql, $params];
  }

  private function buildLogsOrder(?string $sort, ?string $dir): string {
    $map = [
      'ts' => 'ts',
      'level' => 'level',
      'message' => 'message',
      'context_json' => 'context_json',
    ];
    $col = $map[$sort ?? ''] ?? 'id';
    $direction = strtolower((string)$dir) === 'desc' ? 'DESC' : 'ASC';
    return $col . ' ' . $direction;
  }
}
