const axios = require('axios');
const WebSocket = require('ws');
const fs = require('fs');
const path = require('path');
const { loadConfig, saveConfig } = require('../core/config');

const MATCHA_BASE = 'https://butter.lat';
const MATCHA_API = `${MATCHA_BASE}/api/matcha`;
const MATCHA_WS = 'wss://butter.lat/api/matcha/ws';

class MatchaService {
  constructor() {
    this.token = null;
    this.user = null;
    this.ws = null;
    this.wsConnected = false;
    this.mainWindow = null;
    this.heartbeatInterval = null;
    this.reconnectTimeout = null;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.lastMessageSentAt = 0;
    this.gameRunning = false;
  }

  // =========================================================================
  // LIFECYCLE
  // =========================================================================

  init(mainWindow) {
    this.mainWindow = mainWindow;
    this.token = this._loadToken();
    if (this.token) {
      this._connectWs();
      this._startHeartbeat();
      // Load cached user info
      const config = loadConfig();
      if (config.matchaUserId && config.matchaHandle) {
        this.user = { id: config.matchaUserId, handle: config.matchaHandle };
      }
    }
  }

  destroy() {
    // Best-effort offline heartbeat
    if (this.token) {
      this.sendHeartbeat('offline').catch(() => {});
    }
    this._stopHeartbeat();
    this._disconnectWs();
    this.mainWindow = null;
  }

  // =========================================================================
  // TOKEN MANAGEMENT
  // =========================================================================

  _loadToken() {
    const config = loadConfig();
    return config.matchaToken || null;
  }

  _saveToken(token) {
    this.token = token;
    saveConfig({ matchaToken: token });
  }

  _saveUser(user) {
    this.user = user;
    if (user) {
      saveConfig({ matchaUserId: user.id, matchaHandle: user.handle });
    }
  }

  _clearAuth() {
    this.token = null;
    this.user = null;
    this._stopHeartbeat();
    this._disconnectWs();
    // Set to null — saveConfig merges, and JSON.stringify preserves null values,
    // but this effectively marks them as cleared for _loadToken() checks
    saveConfig({ matchaToken: null, matchaUserId: null, matchaHandle: null });
  }

  _authHeaders() {
    return this.token ? { Authorization: `Bearer ${this.token}` } : {};
  }

  // =========================================================================
  // AUTH
  // =========================================================================

  async register(username, password, password2) {
    try {
      const res = await axios.post(`${MATCHA_API}/register`, {
        username, password, password2, deferCreate: true
      });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async confirmRegistration(pendingId, proofId) {
    try {
      const res = await axios.post(`${MATCHA_API}/register/confirm`, {
        pendingId, proofId
      });
      if (res.data.token) {
        this._saveToken(res.data.token);
        if (res.data.user) this._saveUser(res.data.user);
        this._connectWs();
        this._startHeartbeat();
      }
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async login(handle, password) {
    try {
      const res = await axios.post(`${MATCHA_API}/login`, { handle, password });
      if (res.data.token) {
        this._saveToken(res.data.token);
        if (res.data.user) this._saveUser(res.data.user);
        this._connectWs();
        this._startHeartbeat();
      }
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async logout() {
    await this.sendHeartbeat('offline').catch(() => {});
    this._clearAuth();
    return { ok: true };
  }

  getAuthState() {
    return {
      authenticated: !!this.token,
      user: this.user,
      wsConnected: this.wsConnected
    };
  }

  // =========================================================================
  // PROFILE
  // =========================================================================

  async getMe() {
    try {
      const res = await axios.get(`${MATCHA_API}/me`, { headers: this._authHeaders() });
      if (res.data?.user) this._saveUser({ id: res.data.user.id, handle: res.data.user.handle });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async getUser(userId) {
    try {
      const res = await axios.get(`${MATCHA_API}/users/${encodeURIComponent(userId)}`, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  // =========================================================================
  // FRIENDS
  // =========================================================================

  async getFriends() {
    try {
      const res = await axios.get(`${MATCHA_API}/friends`, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async sendFriendRequest(handle) {
    try {
      const res = await axios.post(`${MATCHA_API}/friends/request`, { toHandle: handle }, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async acceptFriend(requestId) {
    try {
      const res = await axios.post(`${MATCHA_API}/friends/request/accept`, { id: requestId }, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async rejectFriend(requestId) {
    try {
      const res = await axios.post(`${MATCHA_API}/friends/request/reject`, { id: requestId }, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async cancelFriendRequest(requestId) {
    try {
      const res = await axios.post(`${MATCHA_API}/friends/request/cancel`, { id: requestId }, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async removeFriend(friendId) {
    try {
      const res = await axios.post(`${MATCHA_API}/friends/remove`, { friendId }, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  // =========================================================================
  // MESSAGES
  // =========================================================================

  async getMessages(withTarget, cursor, after) {
    try {
      const params = { with: withTarget };
      if (cursor) params.cursor = cursor;
      if (after) params.after = after;
      const res = await axios.get(`${MATCHA_API}/messages`, { headers: this._authHeaders(), params });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async sendMessage(to, body, replyTo) {
    // Enforce 800ms throttle
    const now = Date.now();
    const elapsed = now - this.lastMessageSentAt;
    if (elapsed < 800) {
      return { ok: false, error: 'Please wait before sending another message' };
    }
    this.lastMessageSentAt = now;

    // Try WebSocket first
    if (this.wsConnected && this.ws && this.ws.readyState === WebSocket.OPEN) {
      const msg = { type: 'send', to, body };
      if (replyTo) msg.replyTo = replyTo;
      this.ws.send(JSON.stringify(msg));
      return { ok: true, data: { sent: true, via: 'ws' } };
    }

    // Fallback to HTTP
    try {
      const payload = { to, body };
      if (replyTo) payload.replyTo = replyTo;
      const res = await axios.post(`${MATCHA_API}/messages/send`, payload, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async deleteMessage(messageId) {
    try {
      const res = await axios.post(`${MATCHA_API}/messages/${messageId}/delete`, {}, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  // =========================================================================
  // UNREAD
  // =========================================================================

  async getUnread() {
    try {
      const res = await axios.get(`${MATCHA_API}/unread`, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async clearUnread(withTarget) {
    try {
      const res = await axios.post(`${MATCHA_API}/unread/clear`, { with: withTarget }, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  // =========================================================================
  // HEARTBEAT
  // =========================================================================

  async sendHeartbeat(state) {
    if (!this.token) return;
    try {
      await axios.post(`${MATCHA_API}/heartbeat`, { state: state || 'online' }, { headers: this._authHeaders() });
    } catch (err) {
      console.log('[Matcha] Heartbeat failed:', err.message);
    }
  }

  _startHeartbeat() {
    this._stopHeartbeat();
    this.heartbeatInterval = setInterval(() => {
      const state = this.gameRunning ? 'in_game' : 'online';
      this.sendHeartbeat(state);
    }, 30000);
    // Send initial heartbeat
    this.sendHeartbeat('online');
  }

  _stopHeartbeat() {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
    }
  }

  setGameRunning(running) {
    this.gameRunning = running;
  }

  // =========================================================================
  // AVATAR
  // =========================================================================

  async uploadAvatar(filePath, mode) {
    try {
      const crypto = require('crypto');
      const fileBuffer = fs.readFileSync(filePath);
      if (fileBuffer.length > 1024 * 1024) {
        return { ok: false, error: 'Avatar too large (max 1MB)' };
      }
      const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
      const endpoint = mode === 'custom' ? `${MATCHA_API}/avatar/custom` : `${MATCHA_API}/avatar`;
      const res = await axios.post(endpoint, fileBuffer, {
        headers: {
          ...this._authHeaders(),
          'Content-Type': 'image/png',
          'x-avatar-hash': hash,
          'x-avatar-enable': '1',
          'x-avatar-force': '1',
          'Cache-Control': 'no-store'
        },
        maxContentLength: 1024 * 1024
      });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  async deleteAvatar() {
    try {
      const res = await axios.delete(`${MATCHA_API}/avatar`, { headers: this._authHeaders() });
      return { ok: true, data: res.data };
    } catch (err) {
      return this._handleError(err);
    }
  }

  // =========================================================================
  // WEBSOCKET
  // =========================================================================

  _connectWs() {
    if (this.ws) this._disconnectWs();

    try {
      this.ws = new WebSocket(MATCHA_WS);

      this.ws.on('open', () => {
        console.log('[Matcha] WebSocket connected');
        this.reconnectAttempts = 0;
        // Authenticate
        if (this.token) {
          this.ws.send(JSON.stringify({ type: 'auth', token: this.token }));
        }
      });

      this.ws.on('message', (raw) => {
        try {
          const data = JSON.parse(raw.toString());
          this._handleWsMessage(data);
        } catch (err) {
          console.error('[Matcha] WS parse error:', err.message);
        }
      });

      this.ws.on('close', (code) => {
        console.log('[Matcha] WebSocket closed:', code);
        this.wsConnected = false;
        this._sendToRenderer('matcha:ws:disconnected');

        // Handle ban
        if (code === 4003) {
          this._sendToRenderer('matcha:ws:banned', { reason: 'Account banned' });
          return;
        }

        // Auto-reconnect if we have a token
        if (this.token && code !== 4003) {
          this._scheduleReconnect();
        }
      });

      this.ws.on('error', (err) => {
        console.error('[Matcha] WS error:', err.message);
      });
    } catch (err) {
      console.error('[Matcha] WS connect failed:', err.message);
      this._scheduleReconnect();
    }
  }

  _disconnectWs() {
    if (this.reconnectTimeout) {
      clearTimeout(this.reconnectTimeout);
      this.reconnectTimeout = null;
    }
    if (this.ws) {
      try {
        this.ws.close();
      } catch (e) {}
      this.ws = null;
    }
    this.wsConnected = false;
  }

  _scheduleReconnect() {
    if (this.reconnectTimeout) return;
    // Exponential backoff capped at 30s, no hard limit (matches Butter's infinite reconnect)
    const delay = Math.min(2000 * Math.pow(2, this.reconnectAttempts), 30000);
    this.reconnectAttempts++;
    // Notify renderer after several failures so it can show a banner
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      this._sendToRenderer('matcha:ws:max-retries');
    }
    console.log(`[Matcha] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
    this.reconnectTimeout = setTimeout(() => {
      this.reconnectTimeout = null;
      if (this.token) this._connectWs();
    }, delay);
  }

  _handleWsMessage(data) {
    switch (data.type) {
      case 'authed':
        this.wsConnected = true;
        if (data.user) this._saveUser(data.user);
        this._sendToRenderer('matcha:ws:connected', { user: this.user });
        break;
      case 'message':
        console.log('[Matcha] WS message received:', JSON.stringify(data).substring(0, 200));
        this._sendToRenderer('matcha:ws:message', data);
        break;
      case 'message_deleted':
        this._sendToRenderer('matcha:ws:message-deleted', data);
        break;
      case 'avatar_updated':
        this._sendToRenderer('matcha:ws:avatar-updated', data);
        break;
      case 'banned':
        this._sendToRenderer('matcha:ws:banned', data);
        break;
      case 'announcement':
        this._sendToRenderer('matcha:ws:announcement', data);
        break;
      case 'error':
        console.log('[Matcha] WS error:', data.message || data.error || JSON.stringify(data));
        // If auth error, treat as ban/disconnect
        if (data.message === 'Not authed' || data.error === 'Not authed') {
          this._clearAuth();
          this._sendToRenderer('matcha:ws:disconnected');
        }
        break;
      default:
        console.log('[Matcha] Unknown WS message type:', data.type);
    }
  }

  manualReconnect() {
    this.reconnectAttempts = 0;
    if (this.token) this._connectWs();
  }

  // =========================================================================
  // HELPERS
  // =========================================================================

  _sendToRenderer(channel, data) {
    if (this.mainWindow && !this.mainWindow.isDestroyed()) {
      this.mainWindow.webContents.send(channel, data);
    }
  }

  _handleError(err) {
    if (err.response) {
      const status = err.response.status;
      const msg = err.response.data?.error || err.response.data?.message || err.message;
      if (status === 401) {
        // Token expired or invalid
        this._clearAuth();
        this._sendToRenderer('matcha:ws:disconnected');
      }
      return { ok: false, error: msg, status };
    }
    return { ok: false, error: err.message };
  }
}

module.exports = new MatchaService();
