use crate::models::credential::TuleapCredentials;
use crate::models::ticket::ProcessedTicket;
use crate::models::tracker::WatchedTracker;
use crate::services::activity_state::ActivityState;
use crate::services::tuleap_client::TuleapClient;
use crate::services::{crypto, filter_engine, notifier};
use rusqlite::Connection;
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Emitter};
use tokio::time::{interval, Duration};

pub fn start(
    db: Arc<Mutex<Connection>>,
    encryption_key: [u8; 32],
    http_client: reqwest::Client,
    app_handle: AppHandle,
    activity_state: ActivityState,
) {
    tauri::async_runtime::spawn(async move {
        let mut tick = interval(Duration::from_secs(60));
        loop {
            tick.tick().await;
            poll_all_trackers(&db, &encryption_key, &http_client, &app_handle, &activity_state)
                .await;
        }
    });
}

async fn poll_all_trackers(
    db: &Arc<Mutex<Connection>>,
    encryption_key: &[u8; 32],
    http_client: &reqwest::Client,
    app_handle: &AppHandle,
    activity_state: &ActivityState,
) {
    // 1. Read all enabled trackers from DB
    let trackers = {
        let conn = match db.lock() {
            Ok(c) => c,
            Err(e) => {
                eprintln!("poller: failed to lock db: {}", e);
                return;
            }
        };

        let trackers = match WatchedTracker::list_all_enabled(&conn) {
            Ok(t) => t,
            Err(e) => {
                eprintln!("poller: failed to list trackers: {}", e);
                return;
            }
        };

        trackers
    }; // lock released

    // 2. For each tracker that should_poll, poll it
    for tracker in &trackers {
        if !should_poll(tracker) {
            continue;
        }

        let client = {
            let conn = match db.lock() {
                Ok(c) => c,
                Err(e) => {
                    eprintln!("poller: failed to lock db while preparing client: {}", e);
                    continue;
                }
            };

            let creds = match TuleapCredentials::get_for_project(&conn, &tracker.project_id) {
                Ok(Some(c)) => c,
                Ok(None) => {
                    eprintln!(
                        "poller: no credentials configured for project {} (tracker {})",
                        tracker.project_id, tracker.id
                    );
                    continue;
                }
                Err(e) => {
                    eprintln!(
                        "poller: failed to read credentials for project {}: {}",
                        tracker.project_id, e
                    );
                    continue;
                }
            };

            let password = match crypto::decrypt(encryption_key, &creds.password_encrypted) {
                Ok(p) => p,
                Err(e) => {
                    eprintln!(
                        "poller: failed to decrypt password for project {}: {}",
                        tracker.project_id, e
                    );
                    continue;
                }
            };

            TuleapClient::new(http_client, &creds.tuleap_url, &creds.username, &password)
        };

        poll_single_tracker(db, &client, tracker, app_handle, activity_state).await;
    }
}

fn should_poll(tracker: &WatchedTracker) -> bool {
    let last_polled_at = match &tracker.last_polled_at {
        None => return true, // Never polled
        Some(s) => s,
    };

    let last = match parse_timestamp(last_polled_at) {
        Some(dt) => dt,
        None => {
            eprintln!(
                "poller: failed to parse last_polled_at '{}': unsupported format",
                last_polled_at
            );
            return true; // Treat as never polled on parse error
        }
    };

    let elapsed = chrono::Utc::now().signed_duration_since(last).num_minutes();
    elapsed >= tracker.polling_interval as i64
}

fn parse_timestamp(value: &str) -> Option<chrono::DateTime<chrono::Utc>> {
    if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(value) {
        return Some(dt.with_timezone(&chrono::Utc));
    }

    if let Ok(naive) = chrono::NaiveDateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S%.f") {
        return Some(chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(
            naive,
            chrono::Utc,
        ));
    }

    None
}

async fn poll_single_tracker(
    db: &Arc<Mutex<Connection>>,
    client: &TuleapClient,
    tracker: &WatchedTracker,
    app_handle: &AppHandle,
    activity_state: &ActivityState,
) {
    let poll_key = format!("tuleap:{}", tracker.id);
    activity_state.start_poll(&poll_key, &tracker.project_id, &tracker.tracker_label);

    let _ = app_handle.emit(
        "polling-started",
        serde_json::json!({
            "project_id": &tracker.project_id,
            "tracker_id": &tracker.id,
            "tracker_label": &tracker.tracker_label,
            "source": "scheduled",
        }),
    );

    // 1. Fetch artifacts
    let artifacts = match client.get_artifacts(tracker.tracker_id).await {
        Ok(a) => a,
        Err(e) => {
            eprintln!(
                "poller: failed to fetch artifacts for tracker {}: {}",
                tracker.id, e
            );
            let _ = app_handle.emit(
                "polling-error",
                serde_json::json!({
                    "project_id": &tracker.project_id,
                    "tracker_id": &tracker.id,
                    "tracker_label": &tracker.tracker_label,
                    "source": "scheduled",
                    "error": e,
                }),
            );
            activity_state.finish_poll(&poll_key);
            return;
        }
    };

    // 2. Apply filters
    let filtered = filter_engine::apply_filters(&artifacts, &tracker.filters);

    // 3. Insert new tickets and update last_polled_at
    let new_tickets = {
        let conn = match db.lock() {
            Ok(c) => c,
            Err(e) => {
                eprintln!("poller: failed to lock db for insert: {}", e);
                let _ = app_handle.emit(
                    "polling-error",
                    serde_json::json!({
                        "project_id": &tracker.project_id,
                        "tracker_id": &tracker.id,
                        "tracker_label": &tracker.tracker_label,
                        "source": "scheduled",
                        "error": format!("database lock failed while inserting: {}", e),
                    }),
                );
                activity_state.finish_poll(&poll_key);
                return;
            }
        };

        let mut inserted = Vec::new();

        for artifact in &filtered {
            let artifact_id = artifact.get("id").and_then(|v| v.as_i64()).unwrap_or(0) as i32;

            let artifact_title = artifact
                .get("title")
                .and_then(|v| v.as_str())
                .unwrap_or("")
                .to_string();

            let artifact_data =
                serde_json::to_string(artifact).unwrap_or_else(|_| "{}".to_string());

            match ProcessedTicket::insert_if_new(
                &conn,
                &tracker.project_id,
                &tracker.id,
                artifact_id,
                &artifact_title,
                &artifact_data,
            ) {
                Ok(Some(ticket)) => inserted.push(ticket),
                Ok(None) => {}
                Err(e) => {
                    eprintln!(
                        "poller: failed to insert ticket (artifact {}): {}",
                        artifact_id, e
                    );
                }
            }
        }

        // 4. Update last_polled_at
        if let Err(e) = WatchedTracker::update_last_polled(&conn, &tracker.id) {
            eprintln!(
                "poller: failed to update last_polled_at for tracker {}: {}",
                tracker.id, e
            );
        }

        inserted
    }; // lock released

    // 5. Emit event if new tickets found
    if !new_tickets.is_empty() {
        if let Err(e) = app_handle.emit(
            "new-tickets-detected",
            serde_json::json!({
                "project_id": &tracker.project_id,
                "tracker_id": &tracker.id,
                "tracker_label": &tracker.tracker_label,
                "count": new_tickets.len(),
            }),
        ) {
            eprintln!("poller: failed to emit event: {}", e);
        }

        for ticket in &new_tickets {
            notifier::notify_new_ticket(
                db,
                app_handle,
                &tracker.project_id,
                &ticket.id,
                ticket.artifact_id,
                &ticket.artifact_title,
            );
        }
    }

    let _ = app_handle.emit(
        "polling-finished",
        serde_json::json!({
            "project_id": &tracker.project_id,
            "tracker_id": &tracker.id,
            "tracker_label": &tracker.tracker_label,
            "source": "scheduled",
            "new_tickets_count": new_tickets.len(),
        }),
    );
    activity_state.finish_poll(&poll_key);
}

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

    #[test]
    fn parse_timestamp_supports_rfc3339() {
        let parsed = parse_timestamp("2026-04-16T10:15:30.123Z");
        assert!(parsed.is_some());
    }

    #[test]
    fn parse_timestamp_supports_legacy_sqlite_datetime() {
        let parsed = parse_timestamp("2026-04-16 10:15:30");
        assert!(parsed.is_some());
    }

    #[test]
    fn parse_timestamp_rejects_invalid_values() {
        let parsed = parse_timestamp("not-a-date");
        assert!(parsed.is_none());
    }
}
