import { CliLeaf, CliOneOfInput, CliStringInput } from '@alwaysai/alwayscli';
import { yesCliInput } from '../../cli-inputs';
import { checkUserIsLoggedInComponent } from '../../components/user';
import { getModels } from '../../core/model';
import { echo } from '../../util';

export const modelList = CliLeaf({
  name: 'list',
  description: 'List models',
  namedInputs: {
    yes: yesCliInput,
    owner: CliStringInput({
      description: 'Filter by the owner of the model.'
    }),
    filter: CliStringInput({
      description: 'Filter by a custom string in the id of the model.'
    }),
    sortBy: CliOneOfInput({
      description: 'Sort the results.',
      required: false,
      values: ['ascending', 'descending', 'oldest', 'latest']
    })
  },
  async action(_, { yes, owner, filter, sortBy }) {
    await checkUserIsLoggedInComponent({ yes });
    const models = await getModels({ yes });
    const modelTable: { id: string; version: number; updated_at: string }[] =
      [];

    // Create an array of models dropping the other parameters
    for (const model of models) {
      modelTable.push({
        id: model.id,
        version: model.version,
        updated_at: formatUpdatedAt(model.updated_at)
      });
    }

    // Group models by ids and select the one with the highest version for each group
    const latestModels = Object.values(
      modelTable.reduce((acc, model) => {
        // If this is the first model or a newer version, update the model in the accumulator
        if (!acc[model.id] || acc[model.id].version < model.version) {
          acc[model.id] = model;
        }
        return acc;
      }, {})
    );

    // Filter data if filter provided
    const filteredData = filterModels(latestModels, { owner, id: filter });

    // Sort data if sort flag provided, otherwise use latest.
    sortBy = sortBy || 'latest';
    const sortedData = sortModels(filteredData, sortBy);

    // Transform data for display
    const transformedData = sortedData.map((item) => ({
      id: item.id,
      version: item.version,
      updated_at: item.updated_at.toLocaleString()
    }));

    // Inform user of chosen filters and how many results are given
    if (owner || filter) {
      const filterBy: string[] = [];
      if (owner) filterBy.push(`owner: '${owner}'`);
      if (filter) filterBy.push(`id: '${filter}'`);
      echo(`Filtering by ${filterBy.join(' and ')}`);
    }
    if (transformedData.length < 1) {
      echo(`Did not find any matching results. Try using a different filter`);
    } else {
      echo(
        `Displaying ${transformedData.length}/${latestModels.length} models in the ${sortBy} order.`
      );
      console.table(transformedData);
    }
  }
});

// Filter by ID and description
function filterModels(modelTable, filter: { owner?: string; id?: string }) {
  return modelTable.filter((model) => {
    // Split the model.id into owner and id
    const owner = model.id.split('/')[0];
    const id = model.id.split('/')[1];

    // Filter the results
    const matchesOwner = filter.owner ? owner.includes(filter.owner) : true;
    const matchesId = filter.id ? id.includes(filter.id) : true;

    return matchesOwner && matchesId;
  });
}

// Sort models by sorting flag
function sortModels(models, sortBy: string) {
  let sortedModels = [...models]; // Create a shallow copy to avoid modifying the original array
  switch (sortBy) {
    case 'ascending':
      sortedModels = sortedModels.sort((a, b) =>
        a.id.localeCompare(b.id, undefined, { sensitivity: 'accent' })
      );
      break;
    case 'descending':
      sortedModels = sortedModels.sort((a, b) =>
        b.id.localeCompare(a.id, undefined, { sensitivity: 'accent' })
      );
      break;
    case 'oldest':
      sortedModels = sortedModels.sort((a, b) => a.updated_at - b.updated_at);
      break;
    case 'latest':
      sortedModels = sortedModels.sort((a, b) => b.updated_at - a.updated_at);
      break;
    default:
      console.log('Invalid sorting option');
  }

  return sortedModels;
}

function formatUpdatedAt(updatedAt: string): string {
  let date;

  // Try to convert to a Date object
  if (!isNaN(Number(updatedAt))) {
    // If the value is a numeric string (timestamp in milliseconds)
    date = new Date(Number(updatedAt));
  } else {
    // Otherwise, treat it as an ISO string (e.g., '2022-05-12T21:18:26.596Z')
    date = new Date(updatedAt);
  }

  // If the date is valid, format it as a readable string
  if (!isNaN(date.getTime())) {
    return date;
  } else {
    return 'Invalid Date';
  }
}
