import axios from 'axios';
import httpClient from '../config/httpClient';
import pool from '../config/database';
import environment from '../config/environment';

export interface MonitoredService {
  id: string;
  name: string;
  url: string;
  type: 'internal' | 'custom';
  is_active: boolean;
  created_at: string;
  updated_at: string;
}

export interface PingResult {
  name: string;
  url: string;
  status: 'up' | 'down';
  latencyMs: number;
  error?: string;
  checked_at: string;
}

export interface StatusSummary {
  up: number;
  total: number;
}

class ServiceMonitorService {
  async getAllServices(): Promise<MonitoredService[]> {
    const result = await pool.query(
      'SELECT * FROM monitored_services ORDER BY created_at'
    );
    return result.rows;
  }

  async createService(name: string, url: string, type: string = 'custom'): Promise<MonitoredService> {
    const result = await pool.query(
      `INSERT INTO monitored_services (name, url, type) VALUES ($1, $2, $3) RETURNING *`,
      [name, url, type]
    );
    return result.rows[0];
  }

  async updateService(id: string, data: Partial<Pick<MonitoredService, 'name' | 'url' | 'is_active'>>): Promise<MonitoredService> {
    const fields: string[] = [];
    const values: unknown[] = [];
    let idx = 1;

    if (data.name !== undefined) {
      fields.push(`name = $${idx++}`);
      values.push(data.name);
    }
    if (data.url !== undefined) {
      fields.push(`url = $${idx++}`);
      values.push(data.url);
    }
    if (data.is_active !== undefined) {
      fields.push(`is_active = $${idx++}`);
      values.push(data.is_active);
    }

    if (fields.length === 0) {
      throw new Error('No fields to update');
    }

    values.push(id);
    const result = await pool.query(
      `UPDATE monitored_services SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
      values
    );

    if (result.rowCount === 0) {
      throw new Error('Service not found');
    }
    return result.rows[0];
  }

  async deleteService(id: string): Promise<boolean> {
    const result = await pool.query(
      `DELETE FROM monitored_services WHERE id = $1 AND type = 'custom'`,
      [id]
    );
    return (result.rowCount ?? 0) > 0;
  }

  async pingService(url: string, headers?: Record<string, string>): Promise<PingResult> {
    const start = Date.now();
    try {
      await httpClient.get(url, { timeout: 5000, headers });
      return {
        name: '',
        url,
        status: 'up',
        latencyMs: Date.now() - start,
        checked_at: new Date().toISOString(),
      };
    } catch (error) {
      // Treat any HTTP response (even 4xx) as "service is reachable" only for status.php-type endpoints
      // For API endpoints requiring auth, a 401/403 still means the service is up
      if (axios.isAxiosError(error) && error.response) {
        const status = error.response.status;
        // If we got a response, the service is running (auth errors = service is up)
        if (headers && (status === 401 || status === 403)) {
          return {
            name: '',
            url,
            status: 'up',
            latencyMs: Date.now() - start,
            checked_at: new Date().toISOString(),
          };
        }
      }
      return {
        name: '',
        url,
        status: 'down',
        latencyMs: Date.now() - start,
        checked_at: new Date().toISOString(),
        error: axios.isAxiosError(error)
          ? `${error.code ?? 'ERROR'}: ${error.message}`
          : String(error),
      };
    }
  }

  async pingAll(): Promise<PingResult[]> {
    const services = await this.getAllServices();
    const activeServices = services.filter((s) => s.is_active);

    const internalServices = this.getInternalServices();

    const allTargets = [
      ...activeServices.map((s) => ({ name: s.name, url: s.url, pingUrl: s.url, headers: undefined as Record<string, string> | undefined })),
      ...internalServices,
    ];

    const results = await Promise.all(
      allTargets.map(async (target) => {
        const result = await this.pingService(target.pingUrl, target.headers);
        result.name = target.name;
        result.url = target.url;
        return result;
      })
    );

    // Store ping results in history (fire-and-forget)
    this.storePingResults(results).catch(() => {});

    return results;
  }

  async getPingHistory(serviceId: string): Promise<Array<{ id: number; service_id: string; status: string; response_time_ms: number | null; checked_at: string }>> {
    const result = await pool.query(
      'SELECT * FROM service_ping_history WHERE service_id = $1 ORDER BY checked_at DESC LIMIT 20',
      [serviceId]
    );
    return result.rows;
  }

  private async storePingResults(results: PingResult[]): Promise<void> {
    for (const r of results) {
      const serviceId = r.name || r.url;
      await pool.query(
        'INSERT INTO service_ping_history (service_id, status, response_time_ms, checked_at) VALUES ($1, $2, $3, $4)',
        [serviceId, r.status, r.latencyMs, r.checked_at]
      );
      // Keep only last 20 per service
      await pool.query(
        `DELETE FROM service_ping_history WHERE service_id = $1 AND id NOT IN (
          SELECT id FROM service_ping_history WHERE service_id = $1 ORDER BY checked_at DESC LIMIT 20
        )`,
        [serviceId]
      );
    }
  }

  async getStatusSummary(): Promise<StatusSummary> {
    // Read latest stored ping results instead of triggering a new ping cycle
    try {
      const { rows } = await pool.query(`
        SELECT DISTINCT ON (service_id) service_id, status
        FROM service_ping_history
        ORDER BY service_id, checked_at DESC
      `);
      if (rows.length > 0) {
        return {
          up: rows.filter((r: any) => r.status === 'up').length,
          total: rows.length,
        };
      }
    } catch {
      // Fall through to live ping if no history
    }
    // Fallback: no history yet — do a live ping
    const results = await this.pingAll();
    return {
      up: results.filter((r) => r.status === 'up').length,
      total: results.length,
    };
  }

  private getInternalServices(): Array<{ name: string; url: string; pingUrl: string; headers?: Record<string, string> }> {
    const internal: Array<{ name: string; url: string; pingUrl: string; headers?: Record<string, string> }> = [];

    const { bookstack, vikunja, nextcloudUrl } = environment;

    if (nextcloudUrl) {
      internal.push({
        name: 'Nextcloud',
        url: nextcloudUrl,
        pingUrl: `${nextcloudUrl}/status.php`,
      });
    }

    if (bookstack.url) {
      internal.push({
        name: 'BookStack',
        url: bookstack.url,
        pingUrl: `${bookstack.url}/api/pages?count=1`,
        headers: {
          'Authorization': `Token ${bookstack.tokenId}:${bookstack.tokenSecret}`,
        },
      });
    }

    if (vikunja.url) {
      internal.push({
        name: 'Vikunja',
        url: vikunja.url,
        pingUrl: `${vikunja.url}/api/v1/tasks/all?per_page=1`,
        headers: {
          'Authorization': `Bearer ${vikunja.apiToken}`,
        },
      });
    }

    return internal;
  }
}

export default new ServiceMonitorService();
