//! Instagram data scraping library for Rust.
//!
//! # Example
//!
//! ```no_run
//! use insta_rs::Instagram;
//!
//! #[tokio::main]
//! async fn main() -> insta_rs::Result<()> {
//!     let client = Instagram::builder().build()?;
//!
//!     let user = client.user("instagram").await?;
//!     println!("{} has {} followers", user.username, user.follower_count);
//!
//!     Ok(())
//! }
//! ```
//!
//! # Authentication
//!
//! The client supports multiple authentication methods:
//!
//! ```no_run
//! use insta_rs::{Instagram, SessionData};
//!
//! # async fn example() -> insta_rs::Result<()> {
//! // anonymous access (limited)
//! let client = Instagram::builder().build()?;
//!
//! // with credentials (auto-fetches csrf token)
//! let client = Instagram::builder()
//!     .id("username")
//!     .password("password")
//!     .build()?;
//! client.login().await?;
//!
//! // with existing session
//! let session = SessionData::new("session_id", "csrf_token");
//! let client = Instagram::builder()
//!     .session(session)
//!     .build()?;
//! # Ok(())
//! # }
//! ```
//!
//! # Fetching Data
//!
//! ```no_run
//! use insta_rs::Instagram;
//!
//! # async fn example() -> insta_rs::Result<()> {
//! let client = Instagram::builder().build()?;
//!
//! // fetch user profile
//! let user = client.user("natgeo").await?;
//!
//! // fetch a specific post
//! let post = client.media("ABC123xyz").await?;
//!
//! // fetch reel data
//! let reel = client.reel("DEF456uvw").await?;
//!
//! // fetch user posts with pagination
//! let mut cursor = None;
//! loop {
//!     let page = client.user_posts(&user.id, 12, cursor.as_deref()).await?;
//!     for post in &page.items {
//!         println!("{}: {} likes", post.shortcode, post.like_count);
//!     }
//!     if !page.has_next {
//!         break;
//!     }
//!     cursor = page.cursor;
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Comments and Likes
//!
//! ```no_run
//! use insta_rs::Instagram;
//!
//! # async fn example() -> insta_rs::Result<()> {
//! let client = Instagram::builder().build()?;
//!
//! // fetch comments on a post
//! let comments = client.comments("media_id", 50, None).await?;
//! for comment in &comments.items {
//!     println!("{}: {}", comment.owner.username, comment.text);
//! }
//!
//! // fetch users who liked a post
//! let likers = client.likers("shortcode", 50, None).await?;
//! for liker in &likers.items {
//!     println!("{}", liker.username);
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Search
//!
//! ```no_run
//! use insta_rs::Instagram;
//!
//! # async fn example() -> insta_rs::Result<()> {
//! let client = Instagram::builder().build()?;
//!
//! let results = client.search("rust programming").await?;
//!
//! for user in &results.users {
//!     println!("user: @{}", user.username);
//! }
//! for tag in &results.hashtags {
//!     println!("tag: #{} ({} posts)", tag.name, tag.media_count);
//! }
//! # Ok(())
//! # }
//! ```

mod auth;
mod client;
mod endpoints;
mod error;
mod types;

pub use auth::{Auth, SessionData};
pub use client::{CredentialsBuilder, Instagram, InstagramBuilder};
pub use endpoints::QUERY_HASH;
pub use error::{Error, Result};
pub use types::*;

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

    #[test]
    fn build_anonymous_client() {
        let client = Instagram::builder().build();
        assert!(client.is_ok());
    }

    #[test]
    fn build_with_credentials() {
        let client = Instagram::builder()
            .id("test_user")
            .password("test_pass")
            .build();
        assert!(client.is_ok());
    }

    #[test]
    fn build_with_session() {
        let session = SessionData::new("sess123", "csrf456");
        let client = Instagram::builder().session(session).build();
        assert!(client.is_ok());
    }

    #[test]
    fn session_with_user_id() {
        let session = SessionData::new("sess123", "csrf456").with_user_id("user789");
        assert_eq!(session.user_id, Some("user789".into()));
    }

    #[test]
    fn custom_user_agent() {
        let client = Instagram::builder().user_agent("CustomBot/1.0").build();
        assert!(client.is_ok());
    }

    #[test]
    fn pagination_default() {
        let page: Pagination<Media> = Pagination::default();
        assert!(page.items.is_empty());
        assert!(!page.has_next);
        assert!(page.cursor.is_none());
    }

    #[test]
    fn endpoint_urls() {
        let url = endpoints::profile_url("testuser");
        assert!(url.contains("testuser"));

        let url = endpoints::user_info_url("12345");
        assert!(url.contains("12345"));

        let url = endpoints::media_info_url("ABC");
        assert!(url.contains("ABC"));
    }

    #[test]
    fn action_urls() {
        let url = endpoints::like_url("123");
        assert!(url.contains("123") && url.contains("like"));

        let url = endpoints::follow_url("456");
        assert!(url.contains("456") && url.contains("follow"));

        let url = endpoints::comment_url("789");
        assert!(url.contains("789") && url.contains("comment"));
    }

    #[test]
    fn auth_variants() {
        let auth = Auth::None;
        assert!(matches!(auth, Auth::None));

        let auth = Auth::Credentials {
            id: "u".into(),
            password: "p".into(),
        };
        assert!(matches!(auth, Auth::Credentials { .. }));

        let session = SessionData::new("s", "c");
        let auth = Auth::Session(session);
        assert!(matches!(auth, Auth::Session(_)));
    }

    #[test]
    fn media_type_serialization() {
        let reel = MediaType::Reel;
        let json = serde_json::to_string(&reel).unwrap();
        assert_eq!(json, "\"reel\"");

        let carousel = MediaType::Carousel;
        let json = serde_json::to_string(&carousel).unwrap();
        assert_eq!(json, "\"carousel\"");
    }
}

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

    fn client() -> Instagram {
        Instagram::builder().build().unwrap()
    }

    async fn auth_client() -> Option<Instagram> {
        let id = "id";
        let password = "password";

        let _ig = Instagram::builder().id(id).password(password).build();
        match &_ig {
            Ok(_) => println!("Ok"),
            Err(err) => println!("{:?}", err),
        }
        let ig = _ig.ok()?;

        let res = ig.login().await;
        println!("{:?}", res);
        res.ok()?;
        println!("gurt: yo");
        Some(ig)
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_user_profile() {
        let ig = client();
        let user = ig.user("instagram").await.unwrap();
        assert_eq!(user.username, "instagram");
        assert!(user.follower_count > 0);
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_media_post() {
        let ig = client();
        let media = ig.media("CvIBvWQPxKB").await.unwrap();
        assert!(!media.id.is_empty());
        assert!(!media.shortcode.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_reel() {
        let Some(ig) = auth_client().await else {
            println!("Fail");
            return;
        };
        let reel = ig.reel("DNaaHuTRxKF").await.unwrap();
        assert!(!reel.id.is_empty());
        assert!(!reel.video_url.is_empty());
        assert!(reel.duration > 0.0);
        println!("{:?}", reel);
    }

    #[tokio::test]
    #[ignore]
    async fn search_users() {
        let ig = client();
        let results = ig.search("instagram").await.unwrap();
        assert!(!results.users.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_user_posts() {
        let Some(ig) = auth_client().await else {
            return;
        };
        let user = ig.user("instagram").await.unwrap();
        let posts = ig.user_posts(&user.id, 12, None).await.unwrap();
        assert!(!posts.items.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_comments() {
        let Some(ig) = auth_client().await else {
            return;
        };
        let shortcode = "DSdV9bYkg0G";
        let comments = ig.comments(shortcode, 20, None).await.unwrap();
        println!("{:?}", comments);
        assert!(!comments.items.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_likers() {
        let Some(ig) = auth_client().await else {
            return;
        };
        let likers = ig.likers("CvIBvWQPxKB", 20, None).await.unwrap();
        assert!(!likers.items.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_followers() {
        let Some(ig) = auth_client().await else {
            return;
        };
        let user = ig.user("instagram").await.unwrap();
        let followers = ig.followers(&user.id, 20, None).await.unwrap();
        println!("{:?}", followers);
        assert!(!followers.items.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_following() {
        let Some(ig) = auth_client().await else {
            return;
        };
        let user = ig.user("instagram").await.unwrap();
        let following = ig.following(&user.id, 20, None).await.unwrap();
        assert!(!following.items.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_highlights() {
        let Some(ig) = auth_client().await else {
            return;
        };
        let user = ig.user("instagram").await.unwrap();
        let highlights = ig.highlights(&user.id).await.unwrap();
        assert!(!highlights.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn fetch_hashtag_media() {
        let Some(ig) = auth_client().await else {
            return;
        };
        let media = ig.hashtag_media("rust", 12, None).await.unwrap();
        println!("{:?}", media);
        assert!(!media.items.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn pagination_flow() {
        let Some(ig) = auth_client().await else {
            return;
        };
        let user = ig.user("instagram").await.unwrap();

        let first = ig.user_posts(&user.id, 5, None).await.unwrap();
        assert!(first.has_next);

        let second = ig
            .user_posts(&user.id, 5, first.cursor.as_deref())
            .await
            .unwrap();
        assert!(!second.items.is_empty());
    }

    #[tokio::test]
    #[ignore]
    async fn login_flow() {
        let Some(ig) = auth_client().await else {
            println!("skipping login test / INSTA_ID and INSTA_PASSWORD not set");
            return;
        };
        let user = ig.user("instagram").await.unwrap();
        println!("{:?}", user);
        assert!(!user.id.is_empty());
    }
}
