import query, { transaction } from '../server-utils/query.js';
import { declareHandler } from '../server-utils/routesHandler.js';
import { UserRole } from '../types/serverTypes.js';
import semanticRank from '../utils/semanticSearch.js';

/**
 * This file contains route handlers for devices.
 *
 * A device is a piece of code that will process audio or midi data that
 * can be found in the chain editor.
 */

export const getDevices = declareHandler({
  func: async (req, res) => {
    const searchText = req.body.searchText;

    const deviceQueryResult = await query(
      `
    SELECT id, uuid, name, code,
      (SELECT COALESCE(json_agg(row_to_json((SELECT t FROM (SELECT dt.category, dt.value) t))), '[]'::json) FROM device_tags dt WHERE device_id = devices.id) as tags
    FROM devices WHERE superseded_by IS NULL AND name ILIKE '%' || $1 || '%'
    `,
      [searchText]
    );
    const data = deviceQueryResult.rows.map((device) => ({
      sourceId: device.uuid,
      name: device.name,
      code: device.code,
      categories: device.tags.map((t) => t.value),
    }));

    res.send(data);
  },
});

export const searchDevices = declareHandler({
  func: async (req, res) => {
    const deviceQueryResult = await query(
      `
    SELECT id, uuid, name, code,
      (SELECT COALESCE(json_agg(row_to_json((SELECT t FROM (SELECT dt.category, dt.value) t))), '[]'::json) FROM device_tags dt WHERE device_id = devices.id) as tags
    FROM devices WHERE superseded_by IS NULL
    `,
      []
    );
    const data = deviceQueryResult.rows.map((device) => ({
      sourceId: device.uuid,
      name: device.name,
      code: device.code,
      categories: device.tags.map((t) => t.value),
    }));

    res.send(data);
  },
});

export const getDeviceByPreset = declareHandler({
  func: async (req, res) => {
    const searchText = req.body.searchText;

    const deviceQueryResult = await query(
      `
    SELECT DISTINCT
      d.name as device_name,
      d.uuid as device_uuid,
      p.name as preset_name,
      p.uuid as preset_uuid,
      (CASE WHEN p.name ILIKE '%' || $1 || '%' THEN 1 ELSE 2 END) as sort,
      p.created_at,
      ptv.tags as preset_tags,
      dtv.categories as device_categories
    FROM presets p
    JOIN preset_tags pt ON p.id = pt.preset_id
    JOIN LATERAL (SELECT COALESCE(array_agg(pt.value), '{}'::text[]) AS tags
        FROM preset_tags pt
        WHERE pt.preset_id = p.id
    ) ptv ON true
    JOIN devices d ON p.device_id = d.id
    JOIN device_tags dt ON d.id = dt.device_id
    JOIN LATERAL (SELECT COALESCE(array_agg(dt.value), '{}'::text[]) AS categories
        FROM device_tags dt
        WHERE dt.device_id = d.id
        AND dt.category = 'Category'
    ) dtv ON true
    WHERE (pt.value ILIKE '%' || $1 || '%' OR p.name ILIKE '%' || $1 || '%')
    AND p.superseded_by IS NULL
    AND d.superseded_by IS NULL
    ORDER BY sort, p.created_at DESC;
  `,
      [searchText]
    );

    const data = deviceQueryResult.rows.map((row) => ({
      presetName: row.preset_name,
      presetUuid: row.preset_uuid,
      deviceName: row.device_name,
      deviceUuid: row.device_uuid,
      presetTags: row.preset_tags,
      deviceCategories: row.device_categories,
    }));

    res.send(data);
  },
});

export const getDeviceByUuid = declareHandler({
  func: async (req, res) => {
    const deviceUUID = req.params.uuid;
    const deviceQueryResult = await query(`SELECT uuid AS id, name, code FROM devices WHERE uuid = $1`, [deviceUUID]);
    const device = deviceQueryResult.rows[0];
    if (!device) {
      res.status(404).send({ error: true, message: 'Device not found' });
    } else {
      res.send(device);
    }
  },
});

export const getPresetsForDevice = declareHandler({
  func: async (req, res) => {
    const deviceUUID = req.params.uuid;

    const presetsQueryResult = await query(
      `
    SELECT presets.uuid, presets.name,
        json_agg(json_build_object('category', preset_tags.category, 'value', preset_tags.value)) AS tags
    FROM presets
      LEFT JOIN preset_tags ON preset_tags.preset_id = presets.id
    WHERE device_id = (SELECT id FROM devices WHERE uuid = $1) AND superseded_by IS NULL
    GROUP BY presets.id
    `,
      [deviceUUID]
    );

    const presets = presetsQueryResult.rows.map((preset) => ({
      name: preset.name,
      id: preset.uuid,
      tags: preset.tags.reduce(
        (acc, cur) => ({
          ...acc,
          [cur.category]: acc[cur.category] ? [...acc[cur.category], cur.value] : [cur.value],
        }),
        {}
      ),
    }));

    res.send(presets);
  },
});

export const getPresetForTrack = declareHandler({
  func: async (req, res) => {
    const fetchPresetTags = async (presetId: string) => {
      const tagsQueryResult = await query(`SELECT category, value FROM preset_tags WHERE preset_id = $1`, [presetId]);
      const tags: { [key: string]: string[] } = {};

      tagsQueryResult.rows.forEach(({ category = 'Tag', value }) => {
        if (!tags[category]) {
          tags[category] = [];
        }
        tags[category].push(value);
      });

      return tags;
    };

    const suitableDefaultTags = await query(
      `
  SELECT DISTINCT value
  FROM preset_tags
  WHERE category = 'Suitable Default For'
  AND preset_id IN (
    SELECT id FROM presets WHERE superseded_by IS NULL AND device_id IN (
      SELECT id FROM devices WHERE superseded_by IS NULL
    )
  )
`,
      []
    );

    if (suitableDefaultTags.rows.length === 0) {
      res.send({ success: false });
      return;
    }
    const bestTag = (
      await semanticRank(
        `Musical ${req.params.trackName} Synth`,
        suitableDefaultTags.rows.map((r) => `Musical ${r.value} Synth`)
      )
    )[0].option
      .replace('Musical ', '')
      .replace(' Synth', '');

    if (!bestTag) {
      res.send({ success: false });
      return;
    }

    const presetQueryResult = await query(
      `
    SELECT
      presets.name AS preset_name,
      presets.uuid AS preset_uuid,
      presets.id AS preset_id,
      presets.settings AS preset_settings,
      devices.name AS device_name,
      devices.uuid AS device_uuid,
      devices.code AS device_code
    FROM presets
    JOIN devices ON devices.id = presets.device_id
    WHERE presets.id IN (
      SELECT preset_id FROM preset_tags
      WHERE category = 'Suitable Default For'
      AND value = $1
    )
    AND presets.superseded_by IS NULL
    AND presets.device_id IN (
      SELECT id FROM devices WHERE superseded_by IS NULL
    )
    LIMIT 1
  `,
      [bestTag]
    );

    if (presetQueryResult.rows.length === 0) {
      res.send({ success: false });
      return;
    }

    const presetTags = await fetchPresetTags(presetQueryResult.rows[0].preset_id);

    res.send({
      success: true,
      preset: {
        name: presetQueryResult.rows[0].preset_name,
        id: presetQueryResult.rows[0].preset_uuid,
        settings: presetQueryResult.rows[0].preset_settings,
        tags: presetTags,
      },
      device: {
        name: presetQueryResult.rows[0].device_name,
        sourceId: presetQueryResult.rows[0].device_uuid,
        code: presetQueryResult.rows[0].device_code,
      },
    });
  },
});

export const saveDevicePreset = declareHandler({
  func: async (req, res) => {
    if (req.user?.role !== UserRole.Admin) {
      res.status(401);
      res.send({ status: 'error', message: 'Not authorized' });
      return;
    }

    const deviceUUID = req.params.uuid;
    try {
      const insertedId = await transaction(async (query) => {
        const deviceQueryResult = await query(`SELECT id FROM devices WHERE uuid = $1`, [deviceUUID]);
        const deviceId = deviceQueryResult.rows[0].id;
        const overriddenPreset = await query(
          `SELECT * FROM presets WHERE device_id = $1 AND name = $2 AND superseded_by IS NULL`,
          [deviceId, req.body.preset.name]
        );
        const insertQueryResult = await query(
          `INSERT INTO presets (device_id, name, settings, author_id) VALUES ($1, $2, $3, $4) RETURNING *`,
          [deviceId, req.body.preset.name, req.body.preset.settings, req.user.id]
        );
        if (overriddenPreset.rows.length > 0) {
          await query(`UPDATE presets SET superseded_by = $1 WHERE id = $2`, [
            insertQueryResult.rows[0].id,
            overriddenPreset.rows[0].id,
          ]);
        }
        const tagCategories = Object.keys(req.body.preset.tags);
        for (let i = 0; i < tagCategories.length; i++) {
          const category = tagCategories[i];
          const values = req.body.preset.tags[category];
          for (let j = 0; j < values.length; j++) {
            await query(
              `INSERT INTO preset_tags (preset_id, category, value) VALUES ($1, $2, $3) ON CONFLICT (preset_id, category, value) DO NOTHING`,
              [insertQueryResult.rows[0].id, category, values[j]]
            );
          }
        }
        return insertQueryResult.rows[0].id;
      });
      res.send({ preset: { ...req.body.preset, id: insertedId } });
    } catch (e) {
      console.error(e);
      res.status(500);
      res.send({ status: 'error', message: 'Internal server error' });
      return;
    }
  },
});

export const getPresetSettings = declareHandler({
  func: async (req, res) => {
    const presetUUID = req.params.uuid;
    const presetQueryResult = await query(`SELECT settings FROM presets WHERE uuid = $1`, [presetUUID]);
    const preset = presetQueryResult.rows[0];

    res.send(preset?.settings);
  },
});

export const getPresetByUuid = declareHandler({
  func: async (req, res) => {
    const presetUUID = req.params.uuid;
    const presetQueryResult = await query(`SELECT name, settings FROM presets WHERE uuid = $1`, [presetUUID]);
    const preset = presetQueryResult.rows[0];

    res.send({
      settings: preset?.settings,
      name: preset?.name,
    });
  },
});
