use std::time::Instant;

use url::Url;

use super::client::SearchBuilder;
use super::fetch::fetch_page;
use super::form::{effective_query, first_form};
use super::http::build_client;
use super::state::FetchState;
use super::types::{RateLimitJson, SearchMeta, SearchResponse};
use crate::rate_limit::RateLimiter;
use crate::{Error, Result};

pub(crate) async fn execute(builder: SearchBuilder) -> Result<SearchResponse> {
    let start = Instant::now();
    let endpoint_url =
        Url::parse(&builder.options.endpoint).map_err(|e| Error::Usage(e.to_string()))?;
    let limiter = (!builder.options.no_rate_limit).then(|| {
        RateLimiter::new(
            builder.options.state_dir.clone(),
            builder.options.proxy.as_deref(),
            builder.options.limits,
            builder.options.clock.clone(),
        )
        .with_progress_hook(builder.options.progress_hook.clone())
    });
    let http = build_client(&builder.options)?;
    let mut state = FetchState::new(builder.page.saturating_mul(builder.options.num));
    while state.should_fetch() {
        let fields = state.next.take().unwrap_or_else(|| first_form(&builder));
        let page = fetch_page(
            &http,
            &builder.options,
            &endpoint_url,
            fields,
            limiter.as_ref(),
            &mut state,
        )
        .await?;
        state.accept_page(page, &builder)?;
    }
    let results = state.slice_results(builder.page, builder.options.num);
    Ok(SearchResponse {
        schema: 1,
        query: effective_query(&builder),
        kind: "web",
        region: builder.options.region.code().to_owned(),
        page: builder.page,
        count: results.len(),
        instant_answer: state.instant_answer,
        results,
        meta: SearchMeta {
            rate_limit: RateLimitJson {
                next_allowed_at: state.snapshot.next_allowed_at.clone(),
                blocked_until: state.snapshot.blocked_until.clone(),
                slowdown_until: state.snapshot.slowdown_until.clone(),
                retried_count: state.retried_count,
            },
            fetched_pages: state.fetched_pages,
            elapsed_ms: start.elapsed().as_millis(),
        },
    })
}
