-- name: GetUser :one
SELECT * FROM user WHERE id = ? LIMIT 1;

-- name: CreateAuditLog :one
INSERT INTO audit_log (id, user_id, action, resource_type, resource_id, ip, user_agent, metadata_json)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
RETURNING *;

-- name: GetAuditLogsForUser :many
SELECT *
FROM audit_log
WHERE user_id = ?
ORDER BY occurred_at DESC
LIMIT ?;

-- name: GetUserByUsername :one
SELECT * FROM user WHERE username = ? LIMIT 1;

-- name: CreateSession :one
INSERT INTO session (id, user_id, expires_at)
VALUES (?, ?, ?)
RETURNING *;

-- name: GetSession :one
SELECT * FROM session WHERE id = ? LIMIT 1;

-- name: DeleteSession :exec
DELETE FROM session WHERE id = ?;

-- name: RefreshSession :exec
UPDATE session
SET expires_at = ?
WHERE id = ?;

-- name: CreateAPIToken :one
INSERT INTO api_token (id, user_id, token_hash, name)
VALUES (?, ?, ?, ?)
RETURNING *;

-- name: GetAPITokenByHash :one
SELECT * FROM api_token
WHERE token_hash = ? AND revoked_at IS NULL
LIMIT 1;

-- name: TouchAPITokenLastUsedAt :exec
UPDATE api_token
SET last_used_at = CURRENT_TIMESTAMP
WHERE id = ?;

-- name: RevokeAllAPITokensForUser :exec
UPDATE api_token
SET revoked_at = CURRENT_TIMESTAMP
WHERE user_id = ? AND revoked_at IS NULL;

-- name: UpsertAnime :one
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing, duration_seconds)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET
    title_original = excluded.title_original,
    title_english = excluded.title_english,
    title_japanese = excluded.title_japanese,
    image_url = excluded.image_url,
    airing = excluded.airing,
    duration_seconds = excluded.duration_seconds
RETURNING *;

-- name: GetAnime :one
SELECT * FROM anime WHERE id = ? LIMIT 1;

-- name: UpsertWatchListEntry :one
INSERT INTO watch_list_entry (id, user_id, anime_id, status, current_episode, current_time_seconds, completed_at, completed_at_estimated, updated_at)
VALUES (
    sqlc.arg(id),
    sqlc.arg(user_id),
    sqlc.arg(anime_id),
    sqlc.arg(status),
    sqlc.arg(current_episode),
    sqlc.arg(current_time_seconds),
    CASE WHEN sqlc.arg(status) = 'completed' THEN CURRENT_TIMESTAMP END,
    0,
    CURRENT_TIMESTAMP
)
ON CONFLICT (user_id, anime_id) DO UPDATE SET
    status = excluded.status,
    current_episode = excluded.current_episode,
    current_time_seconds = excluded.current_time_seconds,
    completed_at = CASE
        WHEN excluded.status != 'completed' THEN NULL
        WHEN watch_list_entry.status = 'completed' THEN COALESCE(watch_list_entry.completed_at, CURRENT_TIMESTAMP)
        ELSE CURRENT_TIMESTAMP
    END,
    completed_at_estimated = CASE
        WHEN excluded.status = 'completed' AND watch_list_entry.status = 'completed'
            THEN watch_list_entry.completed_at_estimated
        ELSE 0
    END,
    updated_at = CURRENT_TIMESTAMP
RETURNING *;

-- name: SaveWatchProgress :exec
UPDATE watch_list_entry
SET current_episode = ?,
    current_time_seconds = ?
WHERE user_id = ? AND anime_id = ?;

-- name: UpsertContinueWatchingEntry :one
INSERT INTO continue_watching_entry (id, user_id, anime_id, current_episode, current_time_seconds, duration_seconds, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (user_id, anime_id) DO UPDATE SET
    current_episode = excluded.current_episode,
    current_time_seconds = excluded.current_time_seconds,
    duration_seconds = excluded.duration_seconds,
    updated_at = CURRENT_TIMESTAMP
RETURNING *;

-- name: GetContinueWatchingEntry :one
SELECT * FROM continue_watching_entry
WHERE user_id = ? AND anime_id = ? LIMIT 1;

-- name: GetContinueWatchingEntries :many
SELECT
    c.id,
    c.user_id,
    c.anime_id,
    c.current_episode,
    c.current_time_seconds,
    c.duration_seconds,
    c.created_at,
    c.updated_at,
    a.title_original,
    a.title_english,
    a.title_japanese,
    a.image_url,
    a.duration_seconds as anime_duration_seconds
FROM continue_watching_entry c
JOIN anime a ON c.anime_id = a.id
WHERE c.user_id = ?
ORDER BY c.updated_at DESC;

-- name: GetContinueWatchingCarouselEntries :many
SELECT
    c.id,
    c.user_id,
    c.anime_id,
    c.current_episode,
    c.current_time_seconds,
    c.duration_seconds,
    c.created_at,
    c.updated_at,
    a.title_original,
    a.title_english,
    a.title_japanese,
    a.image_url,
    a.duration_seconds as anime_duration_seconds
FROM continue_watching_entry c
JOIN anime a ON c.anime_id = a.id
WHERE c.user_id = ?
ORDER BY c.updated_at DESC
LIMIT ?;

-- name: DeleteContinueWatchingEntry :exec
DELETE FROM continue_watching_entry
WHERE user_id = ? AND anime_id = ?;

-- name: GetWatchListEntry :one
SELECT * FROM watch_list_entry
WHERE user_id = ? AND anime_id = ? LIMIT 1;

-- name: GetUserWatchList :many
SELECT 
    e.id,
    e.user_id,
    e.anime_id,
    e.status,
    e.created_at,
    e.updated_at,
    e.current_episode,
    e.last_episode_at,
    e.current_time_seconds,
    e.completed_at,
    e.completed_at_estimated,
    c.current_episode AS playback_current_episode,
    c.current_time_seconds AS playback_current_time_seconds,
    c.updated_at AS playback_updated_at,
    a.title_original,
    a.title_english,
    a.title_japanese,
    a.image_url,
    a.airing
FROM watch_list_entry e
JOIN anime a ON e.anime_id = a.id
LEFT JOIN continue_watching_entry c ON c.user_id = e.user_id AND c.anime_id = e.anime_id
WHERE e.user_id = ?
ORDER BY e.updated_at DESC;

-- name: DeleteWatchListEntry :exec
DELETE FROM watch_list_entry
WHERE user_id = ? AND anime_id = ?;

-- name: GetWatchingAnime :many
SELECT 
    e.*,
    a.title_original,
    a.title_english,
    a.title_japanese,
    a.image_url,
    a.airing
FROM watch_list_entry e
JOIN anime a ON e.anime_id = a.id
WHERE e.user_id = ? AND e.status IN ('watching', 'plan_to_watch') AND a.airing = 1
ORDER BY e.updated_at DESC;
-- name: UpsertAnimeRelation :exec
INSERT INTO anime_relation (anime_id, related_anime_id, relation_type)
VALUES (?, ?, ?)
ON CONFLICT (anime_id, related_anime_id) DO UPDATE SET
    relation_type = excluded.relation_type;

-- name: UpdateAnimeStatus :exec
UPDATE anime SET status = ? WHERE id = ?;

-- name: MarkRelationsSynced :exec
UPDATE anime SET relations_synced_at = CURRENT_TIMESTAMP WHERE id = ?;

-- name: GetAnimeNeedingRelationSync :many
WITH RECURSIVE sequel_chain AS (
    SELECT a.id, a.title_original, a.relations_synced_at, w.updated_at as base_updated_at, 0 as depth
    FROM watch_list_entry w
    JOIN anime a ON w.anime_id = a.id
    WHERE w.status IN ('completed', 'watching')
    
    UNION
    
    SELECT a.id, a.title_original, a.relations_synced_at, sc.base_updated_at, sc.depth + 1
    FROM sequel_chain sc
    JOIN anime_relation r ON sc.id = r.anime_id AND r.relation_type = 'Sequel'
    JOIN anime a ON r.related_anime_id = a.id
    WHERE sc.depth < 10
)
SELECT id, title_original
FROM sequel_chain
WHERE relations_synced_at IS NULL OR relations_synced_at < datetime('now', '-7 days')
GROUP BY id, title_original
ORDER BY MAX(base_updated_at) DESC, MIN(depth) ASC
LIMIT 50;

-- name: GetUpcomingSeasons :many
WITH RECURSIVE sequel_chain AS (
    SELECT 
        w.anime_id as root_id, 
        a.title_original as root_title, 
        r.related_anime_id as current_id, 
        1 as depth,
        w.user_id
    FROM watch_list_entry w
    JOIN anime a ON w.anime_id = a.id
    JOIN anime_relation r ON w.anime_id = r.anime_id
    WHERE w.user_id = ? 
      AND w.status IN ('completed', 'watching') 
      AND r.relation_type = 'Sequel'

    UNION

    SELECT 
        sc.root_id, 
        sc.root_title, 
        r.related_anime_id, 
        sc.depth + 1,
        sc.user_id
    FROM sequel_chain sc
    JOIN anime_relation r ON sc.current_id = r.anime_id
    WHERE r.relation_type = 'Sequel' AND sc.depth < 10
)
SELECT DISTINCT
    related.*,
    sc.root_title AS prequel_title
FROM sequel_chain sc
JOIN anime related ON sc.current_id = related.id
WHERE related.status IN ('Not yet aired', 'Currently Airing')
  AND NOT EXISTS (
      SELECT 1 FROM watch_list_entry we 
      WHERE we.user_id = sc.user_id AND we.anime_id = related.id
  )
ORDER BY related.id DESC;

-- name: GetJikanCache :one
SELECT data FROM jikan_cache
WHERE key = ? AND datetime(expires_at) > CURRENT_TIMESTAMP LIMIT 1;

-- name: GetJikanCacheStale :one
SELECT data FROM jikan_cache
WHERE key = ? AND datetime(expires_at) > datetime(CURRENT_TIMESTAMP, '-14 days') LIMIT 1;

-- name: GetJikanCacheStats :one
SELECT
    COUNT(*) AS total_rows,
    COUNT(*) FILTER (WHERE datetime(expires_at) <= CURRENT_TIMESTAMP) AS expired_rows,
    COALESCE(unixepoch(MIN(expires_at)), 0) AS oldest_expires_at_seconds
FROM jikan_cache;

-- name: SetJikanCache :exec
INSERT INTO jikan_cache (key, data, expires_at)
VALUES (?, ?, ?)
ON CONFLICT (key) DO UPDATE SET
    data = excluded.data,
    expires_at = excluded.expires_at,
    created_at = CURRENT_TIMESTAMP;

-- name: DeleteExpiredJikanCache :exec
DELETE FROM jikan_cache WHERE datetime(expires_at) <= CURRENT_TIMESTAMP;

-- name: EnqueueAnimeFetchRetry :exec
INSERT INTO anime_fetch_retry (anime_id, attempts, next_retry_at, last_error, updated_at)
VALUES (?, 0, CURRENT_TIMESTAMP, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id) DO UPDATE SET
    next_retry_at = CASE
        WHEN anime_fetch_retry.next_retry_at > CURRENT_TIMESTAMP THEN anime_fetch_retry.next_retry_at
        ELSE CURRENT_TIMESTAMP
    END,
    last_error = excluded.last_error,
    updated_at = CURRENT_TIMESTAMP;

-- name: GetDueAnimeFetchRetries :many
SELECT anime_id, attempts, next_retry_at, last_error, created_at, updated_at
FROM anime_fetch_retry
WHERE next_retry_at <= CURRENT_TIMESTAMP
ORDER BY next_retry_at ASC
LIMIT ?;

-- name: MarkAnimeFetchRetryFailed :exec
UPDATE anime_fetch_retry
SET attempts = attempts + 1,
    next_retry_at = datetime(CURRENT_TIMESTAMP, ?),
    last_error = ?,
    updated_at = CURRENT_TIMESTAMP
WHERE anime_id = ?;

-- name: DeleteAnimeFetchRetry :exec
DELETE FROM anime_fetch_retry
WHERE anime_id = ?;

-- name: CountPendingAnimeFetchRetries :one
SELECT COUNT(*)
FROM anime_fetch_retry
WHERE next_retry_at <= CURRENT_TIMESTAMP;

-- name: GetEpisodeAvailabilityCache :one
SELECT anime_id, data, next_refresh_at, retry_until_at, last_attempt_at, last_success_at, failure_count, last_error, updated_at
FROM episode_availability_cache
WHERE anime_id = ? LIMIT 1;

-- name: UpsertEpisodeAvailabilityCache :exec
INSERT INTO episode_availability_cache (
    anime_id,
    data,
    next_refresh_at,
    retry_until_at,
    last_attempt_at,
    last_success_at,
    failure_count,
    last_error,
    updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id) DO UPDATE SET
    data = excluded.data,
    next_refresh_at = excluded.next_refresh_at,
    retry_until_at = excluded.retry_until_at,
    last_attempt_at = excluded.last_attempt_at,
    last_success_at = excluded.last_success_at,
    failure_count = excluded.failure_count,
    last_error = excluded.last_error,
    updated_at = CURRENT_TIMESTAMP;

-- name: MarkEpisodeAvailabilityRefreshFailed :exec
UPDATE episode_availability_cache
SET last_attempt_at = ?,
    failure_count = failure_count + 1,
    last_error = ?,
    next_refresh_at = ?,
    retry_until_at = ?,
    updated_at = CURRENT_TIMESTAMP
WHERE anime_id = ?;

-- name: UpsertEpisodeProviderMapping :exec
INSERT INTO episode_provider_mapping (anime_id, provider, provider_show_id, failed_until, last_error, updated_at)
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (anime_id, provider) DO UPDATE SET
    provider_show_id = excluded.provider_show_id,
    failed_until = excluded.failed_until,
    last_error = excluded.last_error,
    updated_at = CURRENT_TIMESTAMP;

-- name: GetEpisodeProviderMapping :one
SELECT anime_id, provider, provider_show_id, failed_until, last_error, updated_at
FROM episode_provider_mapping
WHERE anime_id = ? AND provider = ? LIMIT 1;

-- name: DeleteExpiredFailedEpisodeProviderMappings :exec
DELETE FROM episode_provider_mapping
WHERE provider_show_id = ''
  AND failed_until <= CURRENT_TIMESTAMP;

-- name: GetTrackedAiringAnimeIDsDueForEpisodeRefresh :many
WITH tracked AS (
    SELECT DISTINCT w.anime_id
    FROM watch_list_entry w
    JOIN anime a ON a.id = w.anime_id
    WHERE a.airing = 1
      AND w.status IN ('watching', 'plan_to_watch')

    UNION

    SELECT DISTINCT c.anime_id
    FROM continue_watching_entry c
    JOIN anime a ON a.id = c.anime_id
    WHERE a.airing = 1
)
SELECT tracked.anime_id
FROM tracked
LEFT JOIN episode_availability_cache e ON e.anime_id = tracked.anime_id
WHERE e.anime_id IS NULL OR e.next_refresh_at IS NULL OR e.next_refresh_at <= CURRENT_TIMESTAMP
ORDER BY tracked.anime_id
LIMIT ?;

-- name: GetAllCachedAnime :many
SELECT data FROM jikan_cache
WHERE key LIKE 'anime:%' AND datetime(expires_at) > CURRENT_TIMESTAMP LIMIT 1000;
