// ACLED - Armed Conflict Location & Event Data.
// Auth strategy (tries in order):
//   1. OAuth Bearer token:   POST /oauth/token -> Authorization header
//   2. Cookie-based session: POST /user/login?_format=json -> session cookie
// Set ACLED_EMAIL and ACLED_PASSWORD in .env. ACLED_USER or ACLED_USERNAME are
// accepted as aliases for ACLED_EMAIL.

import { daysAgo } from '../utils/fetch.mjs';
import '../utils/env.mjs';

const LOGIN_URL = 'https://acleddata.com/user/login?_format=json';
const TOKEN_URL = 'https://acleddata.com/oauth/token';
const API_BASE  = 'https://acleddata.com/api/acled/read';

let sessionCache = { cookies: null, token: null, method: null, expires: 0 };

export function resetAcledSessionCache() {
  sessionCache = { cookies: null, token: null, method: null, expires: 0 };
}

export function getAcledConfig(env = process.env) {
  const email = env.ACLED_EMAIL || env.ACLED_USER || env.ACLED_USERNAME || '';
  const password = env.ACLED_PASSWORD || '';
  const missing = [];
  if (!email) missing.push('ACLED_EMAIL');
  if (!password) missing.push('ACLED_PASSWORD');
  return { email, password, configured: missing.length === 0, missing };
}

function acledError(status, error, message, extra = {}) {
  return { status, error, message, ...extra };
}

function safeText(value, max = 200) {
  return String(value || '').replace(/Bearer\s+[A-Za-z0-9._-]+/gi, 'Bearer [redacted]').slice(0, max);
}

async function fetchWithTimeout(fetchImpl, url, init, timeoutMs) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    return await fetchImpl(url, { ...init, signal: controller.signal });
  } finally {
    clearTimeout(timer);
  }
}

export async function loginCookie(email, password, opts = {}) {
  const fetchImpl = opts.fetchImpl || globalThis.fetch;
  try {
    const res = await fetchWithTimeout(fetchImpl, LOGIN_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: email, pass: password }),
      redirect: 'manual',
    }, 15000);

    const setCookies = res.headers.getSetCookie?.() || [];
    const cookieStr = setCookies.map(c => c.split(';')[0]).join('; ');

    if ((res.ok || (res.status >= 300 && res.status < 400)) && cookieStr) {
      return { ok: true, cookies: cookieStr };
    }

    const errText = await res.text().catch(() => '');
    return acledError('auth_failed', `acled_cookie_http_${res.status}`, `ACLED cookie login failed with HTTP ${res.status}`, {
      detail: safeText(errText),
    });
  } catch (e) {
    return acledError('auth_failed', 'acled_cookie_request_failed', `ACLED cookie login error: ${e.message}`);
  }
}

export async function loginOAuth(email, password, opts = {}) {
  const fetchImpl = opts.fetchImpl || globalThis.fetch;
  try {
    const body = new URLSearchParams({
      username: email,
      password,
      grant_type: 'password',
      client_id: 'acled',
    });

    const res = await fetchWithTimeout(fetchImpl, TOKEN_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: body.toString(),
    }, 15000);

    if (!res.ok) {
      const errText = await res.text().catch(() => '');
      return acledError('auth_failed', `acled_oauth_http_${res.status}`, `ACLED OAuth failed with HTTP ${res.status}`, {
        detail: safeText(errText),
      });
    }

    const data = await res.json();
    if (!data.access_token) {
      return acledError('auth_failed', 'acled_oauth_missing_access_token', 'ACLED OAuth response did not include access_token');
    }

    return { ok: true, token: data.access_token };
  } catch (e) {
    return acledError('auth_failed', 'acled_oauth_request_failed', `ACLED OAuth error: ${e.message}`);
  }
}

export async function authenticate(opts = {}) {
  const env = opts.env || process.env;
  const fetchImpl = opts.fetchImpl || globalThis.fetch;
  const config = getAcledConfig(env);
  if (!config.configured) {
    return acledError('no_credentials', 'missing_acled_credentials', 'No ACLED credentials. Set ACLED_EMAIL and ACLED_PASSWORD in .env.', {
      missing: config.missing,
    });
  }

  if (sessionCache.method && Date.now() < sessionCache.expires) {
    return sessionCache;
  }

  const diagnostics = [];
  const oauthResult = await loginOAuth(config.email, config.password, { fetchImpl });
  if (oauthResult.ok) {
    sessionCache = { cookies: null, token: oauthResult.token, method: 'oauth', expires: Date.now() + 23 * 60 * 60 * 1000 };
    return sessionCache;
  }
  diagnostics.push({ method: 'oauth', status: oauthResult.status, error: oauthResult.error, message: oauthResult.message });
  if (opts.debug) console.error(`[ACLED DEBUG] OAuth failed: ${oauthResult.error}`);

  const cookieResult = await loginCookie(config.email, config.password, { fetchImpl });
  if (cookieResult.ok) {
    sessionCache = { cookies: cookieResult.cookies, token: null, method: 'cookie', expires: Date.now() + 12 * 60 * 60 * 1000 };
    return sessionCache;
  }
  diagnostics.push({ method: 'cookie', status: cookieResult.status, error: cookieResult.error, message: cookieResult.message });
  if (opts.debug) console.error(`[ACLED DEBUG] Cookie login failed: ${cookieResult.error}`);

  return acledError('auth_failed', 'acled_auth_failed', 'All ACLED auth methods failed.', { diagnostics });
}

function authHeaders(session) {
  const headers = { 'User-Agent': 'Crucix/2.0', 'Content-Type': 'application/json' };
  if (session.method === 'cookie' && session.cookies) {
    headers['Cookie'] = session.cookies;
  } else if (session.method === 'oauth' && session.token) {
    headers['Authorization'] = `Bearer ${session.token}`;
  }
  return headers;
}

export const EVENT_TYPES = [
  'Battles',
  'Explosions/Remote violence',
  'Violence against civilians',
  'Protests',
  'Riots',
  'Strategic developments',
];

export async function getEvents(opts = {}) {
  const {
    limit = 500,
    eventDateStart,
    eventDateEnd,
    eventType,
    country,
    region,
    env = process.env,
    fetchImpl = globalThis.fetch,
    debug = process.argv.includes('--debug'),
  } = opts;

  const session = await authenticate({ env, fetchImpl, debug });
  if (session.error) return session;

  const params = new URLSearchParams({ _format: 'json', limit: String(limit) });
  if (eventDateStart && eventDateEnd) {
    params.set('event_date', `${eventDateStart}|${eventDateEnd}`);
    params.set('event_date_where', 'BETWEEN');
  }
  if (eventType) params.set('event_type', eventType);
  if (country) params.set('country', country);
  if (region) params.set('region', String(region));

  try {
    const url = `${API_BASE}?${params}`;
    if (debug) console.error(`[ACLED DEBUG] Data request: GET ${url}`);
    const res = await fetchWithTimeout(fetchImpl, url, { headers: authHeaders(session) }, 25000);
    if (debug) console.error(`[ACLED DEBUG] Data response: HTTP ${res.status}`);

    if (!res.ok) {
      const errText = await res.text().catch(() => '');
      if (res.status === 401 || res.status === 403) {
        sessionCache = { cookies: null, token: null, method: null, expires: 0 };
        return acledError('access_denied', `acled_data_http_${res.status}`, `ACLED data access denied with HTTP ${res.status}`, {
          authMethod: session.method,
          detail: safeText(errText, 300),
          hint: 'Accept ACLED terms, complete profile fields, and confirm API access for the account.',
        });
      }
      return acledError('api_failed', `acled_data_http_${res.status}`, `ACLED data request failed with HTTP ${res.status}`, {
        detail: safeText(errText),
      });
    }

    const data = await res.json();
    if (data?.status && data.status !== 200) {
      return acledError('api_failed', `acled_api_status_${data.status}`, `ACLED API returned status ${data.status}`, {
        detail: safeText(data.message),
      });
    }

    return data;
  } catch (e) {
    if (e.name === 'AbortError') {
      return acledError('api_failed', 'acled_data_timeout', 'ACLED data request timed out after 25s');
    }
    return acledError('api_failed', 'acled_data_request_failed', `ACLED data error: ${e.message}`);
  }
}

function groupBy(events, field) {
  const map = {};
  for (const e of events) {
    const key = e[field] || 'Unknown';
    if (!map[key]) map[key] = { count: 0, fatalities: 0 };
    map[key].count += 1;
    map[key].fatalities += parseInt(e.fatalities, 10) || 0;
  }
  return map;
}

export async function briefing(opts = {}) {
  const env = opts.env || process.env;
  const fetchImpl = opts.fetchImpl || globalThis.fetch;
  const config = getAcledConfig(env);
  if (!config.configured) {
    return {
      source: 'ACLED',
      timestamp: new Date().toISOString(),
      status: 'no_credentials',
      error: 'missing_acled_credentials',
      missing: config.missing,
      message: 'Set ACLED_EMAIL and ACLED_PASSWORD in .env. Register at https://acleddata.com/user/register',
    };
  }

  const start = daysAgo(7);
  const end = daysAgo(0);

  const data = await getEvents({
    eventDateStart: start,
    eventDateEnd: end,
    limit: 2000,
    env,
    fetchImpl,
    debug: opts.debug,
  });

  if (data?.error) {
    return {
      source: 'ACLED',
      timestamp: new Date().toISOString(),
      status: data.status || 'api_failed',
      error: data.error,
      message: data.message,
      detail: data.detail,
      hint: data.hint,
      diagnostics: data.diagnostics,
    };
  }

  let events = data?.data || [];
  events = events.map(e => ({
    ...e,
    lat: parseFloat(e.latitude) || null,
    lon: parseFloat(e.longitude) || null,
  }));

  const totalFatalities = events.reduce(
    (sum, e) => sum + (parseInt(e.fatalities, 10) || 0), 0
  );

  const byRegion = groupBy(events, 'region');
  const byType = groupBy(events, 'event_type');
  const byCountry = groupBy(events, 'country');
  const topCountries = Object.entries(byCountry)
    .sort((a, b) => b[1].count - a[1].count)
    .slice(0, 10)
    .reduce((obj, [k, v]) => { obj[k] = v; return obj; }, {});

  const deadliestEvents = events
    .filter(e => parseInt(e.fatalities, 10) > 0)
    .sort((a, b) => (parseInt(b.fatalities, 10) || 0) - (parseInt(a.fatalities, 10) || 0))
    .slice(0, 15)
    .map(e => ({
      date: e.event_date,
      type: e.event_type,
      subType: e.sub_event_type,
      country: e.country,
      location: e.location,
      fatalities: parseInt(e.fatalities, 10) || 0,
      lat: parseFloat(e.latitude) || null,
      lon: parseFloat(e.longitude) || null,
      notes: e.notes?.slice(0, 200),
    }));

  return {
    source: 'ACLED',
    timestamp: new Date().toISOString(),
    status: 'ok',
    period: { start, end },
    totalEvents: events.length,
    totalFatalities,
    byRegion,
    byType,
    topCountries,
    deadliestEvents,
  };
}

if (process.argv[1]?.endsWith('acled.mjs')) {
  const data = await briefing();
  console.log(JSON.stringify(data, null, 2));
}
