use indieweb::http::Client;
use indieweb::standards::indieauth::AccessToken;
use indieweb::standards::micropub::query::{Query, QueryKind, Response, SourceQuery};
use indieweb::standards::micropub::paging;
use serde::Serialize;
use serde_json::Value;
use url::Url;

#[derive(Serialize)]
pub struct ListResult {
    pub items: Vec<ListItem>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pagination: Option<Pagination>,
}

#[derive(Serialize)]
pub struct ListItem {
    pub url: Option<String>,
    pub post_type: Option<String>,
    pub published: Option<String>,
    pub name: Option<String>,
    pub content: Option<String>,
}

#[derive(Serialize)]
pub struct Pagination {
    pub before: Option<String>,
    pub after: Option<String>,
}

fn extract_string(props: &Value, key: &str) -> Option<String> {
    props.get(key)
        .and_then(|v| v.as_array())
        .and_then(|arr| arr.first())
        .and_then(|v| {
            if v.is_string() {
                v.as_str().map(ToString::to_string)
            } else if v.is_object() {
                v.get("value")
                    .or_else(|| v.get("html"))
                    .and_then(|v| v.as_str())
                    .map(ToString::to_string)
            } else {
                None
            }
        })
}

pub async fn run(
    client: &impl Client,
    endpoint: &Url,
    access_token: &AccessToken,
    post_type: Option<Vec<String>>,
    limit: Option<usize>,
    offset: Option<usize>,
    url: Option<Url>,
) -> miette::Result<ListResult> {
    let post_types: Vec<indieweb::algorithms::ptd::Type> = post_type
        .unwrap_or_default()
        .iter()
        .filter_map(|pt| pt.parse().ok())
        .collect();

    let source_query = SourceQuery {
        url,
        post_type: post_types,
        ..Default::default()
    };

    let query = Query {
        pagination: paging::Query {
            limit: limit.map(|l| l.to_string()),
            offset: offset.map(|o| o.to_string()),
            ..Default::default()
        },
        kind: QueryKind::Source(Box::new(source_query)),
    };

    let response = query.send(client, endpoint, access_token).await?;

    match response {
        Response::SourceList(list) => {
            Ok(ListResult {
                items: list.items.iter().map(|item| {
                    let props = serde_json::to_value(&item.item.properties).unwrap_or_default();
                    ListItem {
                        url: extract_string(&props, "url"),
                        post_type: item.post_type.first().map(|t| t.to_string()),
                        published: extract_string(&props, "published"),
                        name: extract_string(&props, "name"),
                        content: extract_string(&props, "content"),
                    }
                }).collect(),
                pagination: if list.paging.is_empty() {
                    None
                } else {
                    Some(Pagination {
                        before: list.paging.paging.before.clone(),
                        after: list.paging.paging.after.clone(),
                    })
                },
            })
        }
        Response::Source(source) => {
            let props = serde_json::to_value(&source.item.properties).unwrap_or_default();
            Ok(ListResult {
                items: vec![ListItem {
                    url: extract_string(&props, "url"),
                    post_type: source.post_type.first().map(|t| t.to_string()),
                    published: extract_string(&props, "published"),
                    name: extract_string(&props, "name"),
                    content: extract_string(&props, "content"),
                }],
                pagination: None,
            })
        }
        Response::Error(e) => Err(miette::miette!("Micropub error: {:?}", e)),
        _ => Err(miette::miette!("Unexpected response type")),
    }
}
