<?php

namespace App\Handlers;

require __DIR__ . "/Classes/ApiHandler.php";
require __DIR__ . "/Utils/init.php";

use App\Classes\ApiHandler;
use GuzzleHttp\Client;

header("Content-Type: application/json");

$authHeader = $_SERVER["HTTP_AUTHORIZATION"] ?? "";
$expectedToken = "Bearer " . getenv("NAVIDROME_SCROBBLE_TOKEN");

class NavidromeScrobbleHandler extends ApiHandler
{
  private string $postgrestApiUrl;
  private string $postgrestApiToken;
  private string $navidromeApiUrl;
  private string $navidromeAuthToken;
  private string $forwardEmailApiKey;

  private array $artistCache = [];
  private array $albumCache = [];

  public function __construct()
  {
    parent::__construct();
    $this->ensureCliAccess();
    $this->loadEnvironment();
    $this->validateAuthorization();
  }

  private function loadEnvironment(): void
  {
    $this->postgrestApiUrl = getenv("POSTGREST_URL");
    $this->postgrestApiToken = getenv("POSTGREST_API_KEY");
    $this->navidromeApiUrl = getenv("NAVIDROME_API_URL");
    $this->navidromeAuthToken = getenv("NAVIDROME_API_TOKEN");
    $this->forwardEmailApiKey = getenv("FORWARDEMAIL_API_KEY");
  }

  private function validateAuthorization(): void
  {
    $authHeader = $_SERVER["HTTP_AUTHORIZATION"] ?? "";
    $expectedToken = "Bearer " . getenv("NAVIDROME_SCROBBLE_TOKEN");

    if ($authHeader !== $expectedToken) {
      http_response_code(401);
      echo json_encode(["error" => "Unauthorized."]);
      exit();
    }
  }

  public function runScrobbleCheck(): void
  {
    $recentTracks = $this->fetchRecentlyPlayed();

    if (empty($recentTracks)) return;

    foreach ($recentTracks as $track) {
      if ($this->isTrackAlreadyScrobbled($track)) continue;

      $this->handleTrackScrobble($track);
    }
  }

  private function fetchRecentlyPlayed(): array
  {
    $client = new Client();
    try {
      $response = $client->request("GET", "{$this->navidromeApiUrl}/api/song", [
        "query" => [
          "_end" => 20,
          "_order" => "DESC",
          "_sort" => "play_date",
          "_start" => 0,
          "recently_played" => "true"
        ],
        "headers" => [
          "x-nd-authorization" => "Bearer {$this->navidromeAuthToken}",
          "Accept" => "application/json"
        ]
      ]);

      $data = json_decode($response->getBody()->getContents(), true);
      return $data ?? [];
    } catch (\Exception $e) {
      error_log("Error fetching tracks: " . $e->getMessage());
      return [];
    }
  }

  private function isTrackAlreadyScrobbled(array $track): bool
  {
    $playDate = strtotime($track["playDate"]);
    $existingListen = $this->fetchFromPostgREST("listens", "listened_at=eq.{$playDate}&limit=1");

    return !empty($existingListen);
  }

  private function handleTrackScrobble(array $track): void
  {
    $artistData = $this->getOrCreateArtist($track["artist"]);

    if (empty($artistData)) {
      error_log("Failed to retrieve or create artist: " . $track["artist"]);
      return;
    }

    $albumData = $this->getOrCreateAlbum($track["album"], $artistData);

    if (empty($albumData)) {
      error_log("Failed to retrieve or create album: " . $track["album"]);
      return;
    }

    $this->insertListen($track, $albumData["key"]);
  }

  private function getOrCreateArtist(string $artistName): array
  {
    if (!$this->isDatabaseAvailable()) {
      error_log("Skipping artist insert: database is unavailable.");
      return [];
    }

    if (isset($this->artistCache[$artistName])) return $this->artistCache[$artistName];

    $encodedArtist = rawurlencode($artistName);
    $existingArtist = $this->fetchFromPostgREST("artists", "name_string=eq.{$encodedArtist}&limit=1");

    if (!empty($existingArtist)) {
      $this->artistCache[$artistName] = $existingArtist[0];
      return $existingArtist[0];
    }

    $this->fetchFromPostgREST("artists", "", "POST", [
      "mbid" => null,
      "art" => "4cef75db-831f-4f5d-9333-79eaa5bb55ee",
      "name" => $artistName,
      "slug" => "/music",
      "tentative" => true,
      "total_plays" => 0
    ]);


    $this->sendFailureEmail("New tentative artist record", "A new tentative artist record was inserted:\n\nArtist: $artistName");

    $artistData = $this->fetchFromPostgREST("artists", "name_string=eq.{$encodedArtist}&limit=1");

    $this->artistCache[$artistName] = $artistData[0] ?? [];

    return $this->artistCache[$artistName];
  }

  private function getOrCreateAlbum(string $albumName, array $artistData): array
  {
    if (!$this->isDatabaseAvailable()) {
      error_log("Skipping album insert: database is unavailable.");
      return [];
    }

    $albumKey = $this->generateAlbumKey($artistData["name_string"], $albumName);

    if (isset($this->albumCache[$albumKey])) return $this->albumCache[$albumKey];

    $encodedAlbumKey = rawurlencode($albumKey);
    $existingAlbum = $this->fetchFromPostgREST("albums", "key=eq.{$encodedAlbumKey}&limit=1");

    if (!empty($existingAlbum)) {
      $this->albumCache[$albumKey] = $existingAlbum[0];
      return $existingAlbum[0];
    }

    $artistId = $artistData["id"] ?? null;

    if (!$artistId) {
      error_log("Artist ID missing for album creation: " . $albumName);
      return [];
    }

    $this->fetchFromPostgREST("albums", "", "POST", [
      "mbid" => null,
      "art" => "4cef75db-831f-4f5d-9333-79eaa5bb55ee",
      "key" => $albumKey,
      "name" => $albumName,
      "tentative" => true,
      "total_plays" => 0,
      "artist" => $artistId
    ]);

    $this->sendFailureEmail("New tentative album record", "A new tentative album record was inserted:\n\nAlbum: $albumName\nKey: $albumKey");

    $albumData = $this->fetchFromPostgREST("albums", "key=eq.{$encodedAlbumKey}&limit=1");

    $this->albumCache[$albumKey] = $albumData[0] ?? [];

    return $this->albumCache[$albumKey];
  }

  private function insertListen(array $track, string $albumKey): void
  {
    $playDate = strtotime($track["playDate"]);

    $this->fetchFromPostgREST("listens", "", "POST", [
      "artist_name" => $track["artist"],
      "album_name" => $track["album"],
      "track_name" => $track["title"],
      "listened_at" => $playDate,
      "album_key" => $albumKey
    ]);
  }

  private function generateAlbumKey(string $artistName, string $albumName): string
  {
    $artistKey = sanitizeMediaString($artistName);
    $albumKey = sanitizeMediaString($albumName);

    return "{$artistKey}-{$albumKey}";
  }

  private function sendFailureEmail(string $subject, string $message): void
  {
    if (!$this->isDatabaseAvailable()) {
      error_log("Skipping email: database is unavailable.");
      return;
    }

    $authHeader = "Basic " . base64_encode($this->forwardEmailApiKey . ":");
    $client = new Client([
      "base_uri" => "https://api.forwardemail.net/",
    ]);

    try {
      $response = $client->post("v1/emails", [
        "headers" => [
          "Authorization" => $authHeader,
          "Content-Type" => "application/x-www-form-urlencoded",
        ],
        "form_params" => [
          "from" => "coryd.dev <hi@admin.coryd.dev>",
          "to" => "hi@coryd.dev",
          "subject" => $subject,
          "text" => $message,
        ],
      ]);

    } catch (\GuzzleHttp\Exception\RequestException $e) {
      error_log("Request Exception: " . $e->getMessage());
      if ($e->hasResponse()) {
        $errorResponse = (string) $e->getResponse()->getBody();
        error_log("Error Response: " . $errorResponse);
      }
    } catch (\Exception $e) {
      error_log("General Exception: " . $e->getMessage());
    }
  }

  private function isDatabaseAvailable(): bool
  {
    try {
      $response = $this->fetchFromPostgREST("listens", "limit=1");
      return is_array($response);
    } catch (Exception $e) {
      error_log("Database check failed: " . $e->getMessage());
      return false;
    }
  }
}

try {
  $handler = new NavidromeScrobbleHandler();
  $handler->runScrobbleCheck();
} catch (Exception $e) {
  http_response_code(500);
  echo json_encode(["error" => $e->getMessage()]);
}