//! atproto-static-web   A simple web viewer for AT Proto.
//! Copyright (C) 2025  AverageHelper
//!
//! This program is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by
//! the Free Software Foundation, either version 3 of the License, or
//! (at your option) any later version.
//!
//! This program is distributed in the hope that it will be useful,
//! but WITHOUT ANY WARRANTY; without even the implied warranty of
//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//! GNU General Public License for more details.
//!
//! You should have received a copy of the GNU General Public License
//! along with this program.  If not, see <https://www.gnu.org/licenses/>.

use crate::{
	atproto::{AtUri, Authority, DecentralizedIdentifier, Handle},
	env::Env,
	xrpc::generic::ErrorRes,
};
use log::debug;
use reqwest::Client;
use serde::Deserialize;
use std::str::FromStr;
use url::Url;

pub async fn get_profile(
	client: &Client,
	env: &Env,
	profile: &str,
) -> Result<ProfileViewDetailed, GetProfileError> {
	// https://docs.bsky.app/docs/api/app-bsky-actor-get-profile
	let mut url = env
		.pds_url()
		.join("/xrpc/app.bsky.actor.getProfile")
		.expect("valid url");

	let authority: Authority = profile.parse()?;
	url.query_pairs_mut()
		.append_pair("actor", &authority.to_string())
		.finish();
	debug!("GET {url}");

	let req = client.get(url).build().expect("valid request");
	let res = client.execute(req).await?;
	let status = res.status();
	let text = res.text().await?;
	if !status.is_success() {
		debug!("↗ {status}: {text}");

		// Got an error from upstream
		if let Ok(res) = serde_json::from_str::<ErrorRes>(&text) {
			// If the body gives us an error payload we recognize, handle it appropriately.
			return Err(GetProfileError::Upstream(res.status(), res.message));
		} else {
			// If the error payload isn't known to us, treat the whole body as the message.
			return Err(GetProfileError::Upstream(status, text));
		}
	}
	let data: ProfileViewDetailed = serde_json::from_str(&text)?;
	Ok(data)
}

#[derive(Debug)]
pub enum GetProfileError {
	Deserialize(serde_json::Error),
	InvalidAuthority(<Authority as FromStr>::Err),
	Network(reqwest::Error),
	Upstream(reqwest::StatusCode, String),
}

impl From<<Authority as FromStr>::Err> for GetProfileError {
	fn from(value: <Authority as FromStr>::Err) -> Self {
		Self::InvalidAuthority(value)
	}
}

impl From<serde_json::Error> for GetProfileError {
	fn from(value: serde_json::Error) -> Self {
		Self::Deserialize(value)
	}
}

impl From<reqwest::Error> for GetProfileError {
	fn from(value: reqwest::Error) -> Self {
		Self::Network(value)
	}
}

/// [`app.bsky.actor.profileViewDetailed`](https://github.com/bluesky-social/atproto/blob/757fa34458b6ad20ea2f1a5c6726e326c8b8eae2/lexicons/app/bsky/actor/defs.json#L75)
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProfileViewDetailed {
	pub did: DecentralizedIdentifier,
	pub handle: Handle,
	pub display_name: Option<String>,
	pub description: Option<String>,
	pub avatar: Option<Url>,
	pub banner: Option<Url>,
	pub followers_count: Option<u64>,
	pub follows_count: Option<u64>,
	pub posts_count: Option<u64>,
	pub pinned_post: Option<StrongRef>,
}

/// [`com.atproto.repo.strongRef`](https://github.com/bluesky-social/atproto/blob/757fa34458b6ad20ea2f1a5c6726e326c8b8eae2/lexicons/com/atproto/repo/strongRef.json)
#[derive(Deserialize)]
pub struct StrongRef {
	pub uri: AtUri,
}
