use serde::{Deserialize, Serialize};

// ─── Space types ───

#[derive(Debug, Deserialize, Serialize)]
pub struct ConfluenceSpace {
    pub id: String,
    pub key: String,
    pub name: String,
    #[serde(rename = "type", default)]
    pub space_type: String,
    #[serde(default)]
    pub status: String,
    pub description: Option<SpaceDescription>,
    #[serde(rename = "_links")]
    pub links: Option<SpaceLinks>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SpaceDescription {
    pub plain: Option<PlainValue>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PlainValue {
    pub value: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SpaceLinks {
    pub webui: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SpaceListResponse {
    pub results: Vec<ConfluenceSpace>,
    #[serde(rename = "_links")]
    pub links: Option<PaginationLinks>,
}

// ─── Page types (v2 Cloud) ───

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfluencePage {
    pub id: String,
    #[serde(default)]
    pub status: String,
    pub title: String,
    pub space_id: Option<String>,
    pub parent_id: Option<String>,
    pub version: Option<PageVersion>,
    pub body: Option<PageBody>,
    #[serde(rename = "_links")]
    pub links: Option<PageLinks>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PageVersion {
    pub number: i64,
    pub message: Option<String>,
    pub created_at: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PageBody {
    pub storage: Option<StorageBody>,
    pub view: Option<StorageBody>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct StorageBody {
    pub value: String,
    pub representation: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PageLinks {
    pub webui: Option<String>,
    pub editui: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PageListResponse {
    pub results: Vec<ConfluencePage>,
    #[serde(rename = "_links")]
    pub links: Option<PaginationLinks>,
}

// ─── Page types (v1 Server/DC) ───

#[derive(Debug, Deserialize, Serialize)]
pub struct ConfluencePageV1 {
    pub id: String,
    #[serde(rename = "type", default)]
    pub page_type: String,
    #[serde(default)]
    pub status: String,
    pub title: String,
    pub space: Option<SpaceRef>,
    pub version: Option<PageVersionV1>,
    pub body: Option<PageBody>,
    pub ancestors: Option<Vec<AncestorRef>>,
    #[serde(rename = "_links")]
    pub links: Option<PageLinksV1>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SpaceRef {
    pub key: String,
    pub name: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PageVersionV1 {
    pub number: i64,
    pub when: Option<String>,
    pub by: Option<VersionAuthor>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VersionAuthor {
    pub display_name: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AncestorRef {
    pub id: String,
    pub title: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PageLinksV1 {
    pub webui: Option<String>,
    pub edit: Option<String>,
    #[serde(rename = "self")]
    pub self_link: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct PageListResponseV1 {
    pub results: Vec<ConfluencePageV1>,
    #[serde(rename = "_links")]
    pub links: Option<PaginationLinks>,
}

// ─── Search types ───

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchResult {
    pub results: Vec<SearchResultItem>,
    pub total_size: Option<i64>,
    #[serde(rename = "_links")]
    pub links: Option<PaginationLinks>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchResultItem {
    pub content: Option<ConfluencePageV1>,
    pub title: String,
    pub excerpt: Option<String>,
    pub url: Option<String>,
    pub result_global_container: Option<GlobalContainer>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GlobalContainer {
    pub title: String,
    pub display_url: Option<String>,
}

// ─── Create/Update response ───

#[derive(Debug, Deserialize, Serialize)]
pub struct CreatePageResponse {
    pub id: String,
    pub title: String,
    #[serde(default)]
    pub status: String,
    #[serde(rename = "_links")]
    pub links: Option<PageLinks>,
}

// ─── Label types ───

#[derive(Debug, Deserialize, Serialize)]
pub struct ConfluenceLabel {
    pub prefix: Option<String>,
    pub name: String,
    pub id: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct LabelListResponse {
    pub results: Vec<ConfluenceLabel>,
    #[serde(rename = "_links")]
    pub links: Option<PaginationLinks>,
}

// ─── Attachment types ───

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfluenceAttachment {
    pub id: String,
    #[serde(default)]
    pub status: String,
    pub title: String,
    pub media_type: Option<String>,
    pub file_size: Option<u64>,
    pub download_link: Option<String>,
    #[serde(rename = "_links")]
    pub links: Option<AttachmentLinks>,
    pub extensions: Option<AttachmentExtensions>,
    pub metadata: Option<AttachmentMetadata>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AttachmentLinks {
    pub webui: Option<String>,
    pub download: Option<String>,
    #[serde(rename = "self")]
    pub self_link: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AttachmentExtensions {
    pub media_type: Option<String>,
    pub file_size: Option<u64>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AttachmentMetadata {
    pub media_type: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AttachmentListResponse {
    pub results: Vec<ConfluenceAttachment>,
    #[serde(rename = "_links")]
    pub links: Option<AttachmentListLinks>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AttachmentListLinks {
    pub next: Option<String>,
    pub base: Option<String>,
}

// ─── Common ───

#[derive(Debug, Deserialize, Serialize)]
pub struct PaginationLinks {
    pub next: Option<String>,
}

// ─── Unified page enum for dual-API support ───

/// A page that could come from either the v1 or v2 API.
pub enum AnyPage {
    V1(ConfluencePageV1),
    V2(ConfluencePage),
}

impl AnyPage {
    pub fn id(&self) -> &str {
        match self {
            AnyPage::V1(p) => &p.id,
            AnyPage::V2(p) => &p.id,
        }
    }

    pub fn title(&self) -> &str {
        match self {
            AnyPage::V1(p) => &p.title,
            AnyPage::V2(p) => &p.title,
        }
    }

    pub fn status(&self) -> &str {
        match self {
            AnyPage::V1(p) => &p.status,
            AnyPage::V2(p) => &p.status,
        }
    }

    pub fn version_number(&self) -> i64 {
        match self {
            AnyPage::V1(p) => p.version.as_ref().map_or(1, |v| v.number),
            AnyPage::V2(p) => p.version.as_ref().map_or(1, |v| v.number),
        }
    }

    pub fn storage_value(&self) -> &str {
        match self {
            AnyPage::V1(p) => p
                .body
                .as_ref()
                .and_then(|b| b.storage.as_ref())
                .map_or("", |s| &s.value),
            AnyPage::V2(p) => p
                .body
                .as_ref()
                .and_then(|b| b.storage.as_ref())
                .map_or("", |s| &s.value),
        }
    }

    pub fn webui_link(&self) -> Option<&str> {
        match self {
            AnyPage::V1(p) => p.links.as_ref().and_then(|l| l.webui.as_deref()),
            AnyPage::V2(p) => p.links.as_ref().and_then(|l| l.webui.as_deref()),
        }
    }

    pub fn space_info(&self) -> Option<(&str, &str)> {
        match self {
            AnyPage::V1(p) => p.space.as_ref().map(|s| (s.key.as_str(), s.name.as_str())),
            AnyPage::V2(_) => None,
        }
    }

    pub fn space_id(&self) -> Option<&str> {
        match self {
            AnyPage::V1(_) => None,
            AnyPage::V2(p) => p.space_id.as_deref(),
        }
    }

    pub fn version_date(&self) -> Option<&str> {
        match self {
            AnyPage::V1(p) => p.version.as_ref().and_then(|v| v.when.as_deref()),
            AnyPage::V2(p) => p.version.as_ref().and_then(|v| v.created_at.as_deref()),
        }
    }

    pub fn version_author(&self) -> Option<&str> {
        match self {
            AnyPage::V1(p) => p
                .version
                .as_ref()
                .and_then(|v| v.by.as_ref())
                .map(|a| a.display_name.as_str()),
            AnyPage::V2(_) => None,
        }
    }

    pub fn ancestors(&self) -> Option<&[AncestorRef]> {
        match self {
            AnyPage::V1(p) => p.ancestors.as_deref(),
            AnyPage::V2(_) => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_v1_page() -> ConfluencePageV1 {
        ConfluencePageV1 {
            id: "100".to_string(),
            page_type: "page".to_string(),
            status: "current".to_string(),
            title: "V1 Page".to_string(),
            space: Some(SpaceRef {
                key: "SP".to_string(),
                name: "Space Name".to_string(),
            }),
            version: Some(PageVersionV1 {
                number: 7,
                when: Some("2024-03-15T10:00:00Z".to_string()),
                by: Some(VersionAuthor {
                    display_name: "John".to_string(),
                }),
            }),
            body: Some(PageBody {
                storage: Some(StorageBody {
                    value: "<p>Content</p>".to_string(),
                    representation: Some("storage".to_string()),
                }),
                view: None,
            }),
            ancestors: Some(vec![
                AncestorRef { id: "1".to_string(), title: "Root".to_string() },
                AncestorRef { id: "2".to_string(), title: "Parent".to_string() },
            ]),
            links: Some(PageLinksV1 {
                webui: Some("/pages/100".to_string()),
                edit: None,
                self_link: None,
            }),
        }
    }

    fn make_v2_page() -> ConfluencePage {
        ConfluencePage {
            id: "200".to_string(),
            status: "draft".to_string(),
            title: "V2 Page".to_string(),
            space_id: Some("space-42".to_string()),
            parent_id: Some("parent-1".to_string()),
            version: Some(PageVersion {
                number: 3,
                message: Some("Updated".to_string()),
                created_at: Some("2024-06-01T12:00:00Z".to_string()),
            }),
            body: Some(PageBody {
                storage: Some(StorageBody {
                    value: "<p>V2 Body</p>".to_string(),
                    representation: None,
                }),
                view: None,
            }),
            links: Some(PageLinks {
                webui: Some("/wiki/pages/200".to_string()),
                editui: None,
            }),
        }
    }

    #[test]
    fn any_page_v1_accessors() {
        let page = AnyPage::V1(make_v1_page());
        assert_eq!(page.id(), "100");
        assert_eq!(page.title(), "V1 Page");
        assert_eq!(page.status(), "current");
        assert_eq!(page.version_number(), 7);
        assert_eq!(page.storage_value(), "<p>Content</p>");
        assert_eq!(page.webui_link(), Some("/pages/100"));
        assert_eq!(page.space_info(), Some(("SP", "Space Name")));
        assert_eq!(page.space_id(), None);
        assert_eq!(page.version_date(), Some("2024-03-15T10:00:00Z"));
        assert_eq!(page.version_author(), Some("John"));
        assert_eq!(page.ancestors().unwrap().len(), 2);
    }

    #[test]
    fn any_page_v2_accessors() {
        let page = AnyPage::V2(make_v2_page());
        assert_eq!(page.id(), "200");
        assert_eq!(page.title(), "V2 Page");
        assert_eq!(page.status(), "draft");
        assert_eq!(page.version_number(), 3);
        assert_eq!(page.storage_value(), "<p>V2 Body</p>");
        assert_eq!(page.webui_link(), Some("/wiki/pages/200"));
        assert_eq!(page.space_info(), None);
        assert_eq!(page.space_id(), Some("space-42"));
        assert_eq!(page.version_date(), Some("2024-06-01T12:00:00Z"));
        assert_eq!(page.version_author(), None);
        assert!(page.ancestors().is_none());
    }

    #[test]
    fn any_page_v1_no_version() {
        let mut page = make_v1_page();
        page.version = None;
        let any = AnyPage::V1(page);
        assert_eq!(any.version_number(), 1); // default
        assert_eq!(any.version_date(), None);
        assert_eq!(any.version_author(), None);
    }

    #[test]
    fn any_page_v2_no_version() {
        let mut page = make_v2_page();
        page.version = None;
        let any = AnyPage::V2(page);
        assert_eq!(any.version_number(), 1);
        assert_eq!(any.version_date(), None);
    }

    #[test]
    fn any_page_v1_no_body() {
        let mut page = make_v1_page();
        page.body = None;
        let any = AnyPage::V1(page);
        assert_eq!(any.storage_value(), "");
    }

    #[test]
    fn any_page_v2_no_body() {
        let mut page = make_v2_page();
        page.body = None;
        let any = AnyPage::V2(page);
        assert_eq!(any.storage_value(), "");
    }

    #[test]
    fn any_page_v1_no_links() {
        let mut page = make_v1_page();
        page.links = None;
        let any = AnyPage::V1(page);
        assert_eq!(any.webui_link(), None);
    }

    #[test]
    fn any_page_v1_no_space() {
        let mut page = make_v1_page();
        page.space = None;
        let any = AnyPage::V1(page);
        assert_eq!(any.space_info(), None);
    }

    #[test]
    fn any_page_v1_no_ancestors() {
        let mut page = make_v1_page();
        page.ancestors = None;
        let any = AnyPage::V1(page);
        assert!(any.ancestors().is_none());
    }

    // ── Serde round-trip tests ──

    #[test]
    fn confluence_page_v2_deserialize() {
        let json = r#"{
            "id": "1",
            "status": "current",
            "title": "Test",
            "spaceId": "sp1",
            "version": {"number": 2, "createdAt": "2024-01-01"}
        }"#;
        let page: ConfluencePage = serde_json::from_str(json).unwrap();
        assert_eq!(page.id, "1");
        assert_eq!(page.title, "Test");
        assert_eq!(page.space_id, Some("sp1".to_string()));
        assert_eq!(page.version.unwrap().number, 2);
    }

    #[test]
    fn confluence_page_v1_deserialize() {
        let json = r#"{
            "id": "2",
            "type": "page",
            "status": "current",
            "title": "V1 Test",
            "space": {"key": "DEV", "name": "Dev Space"},
            "version": {"number": 3}
        }"#;
        let page: ConfluencePageV1 = serde_json::from_str(json).unwrap();
        assert_eq!(page.id, "2");
        assert_eq!(page.page_type, "page");
        assert_eq!(page.space.unwrap().key, "DEV");
    }

    #[test]
    fn confluence_label_deserialize() {
        let json = r#"{"name": "important", "prefix": "global", "id": "10"}"#;
        let label: ConfluenceLabel = serde_json::from_str(json).unwrap();
        assert_eq!(label.name, "important");
        assert_eq!(label.prefix, Some("global".to_string()));
    }

    #[test]
    fn confluence_attachment_deserialize() {
        let json = r#"{
            "id": "att1",
            "status": "current",
            "title": "file.pdf",
            "mediaType": "application/pdf",
            "fileSize": 12345
        }"#;
        let att: ConfluenceAttachment = serde_json::from_str(json).unwrap();
        assert_eq!(att.title, "file.pdf");
        assert_eq!(att.file_size, Some(12345));
    }

    #[test]
    fn search_result_deserialize() {
        let json = r#"{
            "results": [{"title": "Found page", "excerpt": "...match..."}],
            "totalSize": 1
        }"#;
        let sr: SearchResult = serde_json::from_str(json).unwrap();
        assert_eq!(sr.results.len(), 1);
        assert_eq!(sr.total_size, Some(1));
    }

    #[test]
    fn space_list_response_deserialize() {
        let json = r#"{
            "results": [{"id": "1", "key": "DEV", "name": "Dev"}]
        }"#;
        let resp: SpaceListResponse = serde_json::from_str(json).unwrap();
        assert_eq!(resp.results.len(), 1);
        assert_eq!(resp.results[0].key, "DEV");
    }
}
