use activitypub::{RemoteReviewRepository, RemoteReviewUpdate};
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use domain::models::{Review, ReviewSource};

use super::PostgresFederationRepository;
use adapter_common::datetime_to_str;

#[async_trait]
impl RemoteReviewRepository for PostgresFederationRepository {
    async fn save_remote_review(
        &self,
        review: &Review,
        ap_id: &str,
        movie_title: &str,
        release_year: u16,
        external_metadata_id: Option<&str>,
        poster_url: Option<&str>,
    ) -> Result<()> {
        let actor_url = match review.source() {
            ReviewSource::Remote { actor_url } => actor_url.clone(),
            ReviewSource::Local => {
                return Err(anyhow!("save_remote_review called with a local review"));
            }
        };
        let movie_id = review.movie_id().value().to_string();
        sqlx::query(
            "INSERT INTO movies (id, external_metadata_id, title, release_year, director, poster_path)
             VALUES ($1, $2, $3, $4, NULL, $5)
             ON CONFLICT(id) DO UPDATE SET
                 external_metadata_id = COALESCE(EXCLUDED.external_metadata_id, movies.external_metadata_id),
                 poster_path = COALESCE(EXCLUDED.poster_path, movies.poster_path)",
        )
        .bind(&movie_id)
        .bind(external_metadata_id)
        .bind(movie_title)
        .bind(release_year.max(1888) as i64)
        .bind(poster_url)
        .execute(&self.pool)
        .await?;

        let id = review.id().value().to_string();
        let user_id = review.user_id().value().to_string();
        let rating = review.rating().value() as i64;
        let comment = review.comment().map(|c| c.value().to_string());
        let watched_at = datetime_to_str(review.watched_at());
        let created_at = datetime_to_str(review.created_at());
        sqlx::query(
            "INSERT INTO reviews (id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url, ap_id)
             VALUES ($1, $2, $3, $4, $5, $6::timestamptz, $7::timestamptz, $8, $9) ON CONFLICT DO NOTHING",
        )
        .bind(&id)
        .bind(&movie_id)
        .bind(&user_id)
        .bind(rating)
        .bind(&comment)
        .bind(&watched_at)
        .bind(&created_at)
        .bind(&actor_url)
        .bind(ap_id)
        .execute(&self.pool)
        .await?;
        Ok(())
    }

    async fn delete_remote_review(&self, ap_id: &str, actor_url: &str) -> Result<()> {
        sqlx::query("DELETE FROM reviews WHERE ap_id = $1 AND remote_actor_url = $2")
            .bind(ap_id)
            .bind(actor_url)
            .execute(&self.pool)
            .await?;
        Ok(())
    }

    async fn update_remote_review(&self, u: RemoteReviewUpdate<'_>) -> Result<()> {
        let watched_at_str = datetime_to_str(&u.watched_at);
        sqlx::query(
            "UPDATE reviews SET rating = $1, comment = $2, watched_at = $3::timestamptz, watch_medium = $4
             WHERE ap_id = $5 AND remote_actor_url = $6",
        )
        .bind(u.rating as i64)
        .bind(u.comment)
        .bind(&watched_at_str)
        .bind(u.watch_medium)
        .bind(u.ap_id)
        .bind(u.actor_url)
        .execute(&self.pool)
        .await?;
        if let Some(url) = u.poster_url {
            sqlx::query(
                "UPDATE movies SET poster_path = $1
                 WHERE id = (SELECT movie_id FROM reviews WHERE ap_id = $2 AND remote_actor_url = $3)",
            )
            .bind(url)
            .bind(u.ap_id)
            .bind(u.actor_url)
            .execute(&self.pool)
            .await?;
        }
        Ok(())
    }

    async fn delete_by_actor(&self, actor_url: &str) -> Result<()> {
        sqlx::query("DELETE FROM reviews WHERE remote_actor_url = $1")
            .bind(actor_url)
            .execute(&self.pool)
            .await?;
        Ok(())
    }
}
