import { createForm } from '@felte/solid';
import {
  Component,
  createSignal,
  Index,
  Match,
  onMount,
  Show,
  Switch,
} from 'solid-js';
import { TextField, Button, Progress } from '@kobalte/core';
import reporter from '@felte/reporter-tippy';
import './style.css';
import { A, useNavigate } from 'solid-start';
import { adminCredentials } from '../credentials';
import { put } from '~/lib/put';
import { del } from '~/lib/del';
import { isFunction } from 'lodash';
import { userId } from '~/lib/user-id';
import { userExists } from '~/lib/user-exists';
import { update } from '~/lib/update';
import { replicationDocument } from '~/lib/replication-document';
import { get } from '~/lib/get';
import { v4 as uuid } from 'uuid';

interface Props {
  values?: () => any;
}

const User: Component<Props> = (props) => {
  const navigate = useNavigate();

  const [progress, setProgress] = createSignal(-1);

  const defaultValues = () => ({});
  const values = props.values ?? defaultValues;
  const [previousSubscriptions, setPreviousSubscriptions] = createSignal(
    values().subscriptions ?? []
  );

  const credentials = adminCredentials();
  const { database } = credentials || { database: null };

  const isNew = () => !isFunction(props?.values);

  const getValues = () => {
    if (isNew()) {
      return { database, subscriptions: [], token: uuid() };
    }
    return { token: uuid(), subscriptions: [], ...values() };
  };

  const invitationLink = () => {
    // TODO: make these URLs configurable
    const configUrl = `https://admin.dyomedea.app/api/conf/${
      getValues().token
    }`;
    return `https://web.dyomedea.app#${btoa(configUrl)}`;
  };

  const submitHandler = async (values: any, context: any) => {
    console.log({
      caller: 'User / submitHandler',
      props,
      values,
      context,
    });
    if (!database) {
      return;
    }

    const { username } = values;

    setProgress(0);
    const id = getValues()?._id ?? userId(username, values.database);
    await put(id, values, isNew());
    const couchUserId = `org.couchdb.user:${username}`;
    const userDoc = {
      _id: couchUserId,
      name: username,
      password: values.password,
      type: 'user',
      roles: [],
    };
    await put(couchUserId, userDoc, isNew(), '_users');

    setProgress(1);

    const updatedSubscriptions = values.subscriptions ?? [];

    const isIn = (username: string, subs: any[]) => {
      for (let i = 0; i < subs.length; i++) {
        let sub = subs[i];
        if (username === sub.username) {
          return true;
        }
      }
      return false;
    };

    console.log({
      caller: 'User / submitHandler',
      previousSubscriptions: previousSubscriptions(),
      updatedSubscriptions,
    });

    const addSubscriptionFactory =
      (subscriptionUserName: string) => (userDocument: any) => {
        const subscriptions = userDocument.subscriptions || [];
        subscriptions.push({ username, direction: '' });
        userDocument.subscriptions = subscriptions;
        return { username: subscriptionUserName, ...userDocument };
      };

    const removeSubscriptionFactory =
      (subscriptionUserName: string) => (userDocument: any) => {
        const subscriptions = userDocument.subscriptions || [];
        userDocument.subscriptions = subscriptions.filter(
          (subscription: any) => subscription.username !== username
        );
        return { username: subscriptionUserName, ...userDocument };
      };

    const defaultUserDocument = {
      database,
    };

    const currentUserId = userId(data('username'), data('database'));
    const credentials = adminCredentials();
    const adminUser = credentials?.username || '';

    for (let i = 0; i < updatedSubscriptions.length; i++) {
      let subscription = updatedSubscriptions[i];
      if (!isIn(subscription.username, previousSubscriptions())) {
        await update(
          userId(subscription.username, database),
          addSubscriptionFactory(subscription.username),
          defaultUserDocument
        );
        const otherUserId = userId(subscription.username, data('database'));
        const otherUserPassword = (await get(otherUserId)).password;
        let repDocument = replicationDocument(
          currentUserId,
          data('password'),
          otherUserId,
          otherUserPassword,
          adminUser
        );
        await put(repDocument._id, repDocument, false, '_replicator');
        repDocument = replicationDocument(
          otherUserId,
          otherUserPassword,
          currentUserId,
          data('password'),
          adminUser
        );
        await put(repDocument._id, repDocument, false, '_replicator');
        console.log({
          caller: 'User / submitHandler / new subscription',
          username: subscription.username,
        });
      }
    }

    setProgress(2);

    for (let i = 0; i < previousSubscriptions().length; i++) {
      let subscription = previousSubscriptions()[i];
      if (!isIn(subscription.username, updatedSubscriptions)) {
        await update(
          userId(subscription.username, database),
          removeSubscriptionFactory(subscription.username),
          defaultUserDocument
        );

        const otherUserId = userId(subscription.username, data('database'));
        await del(`${currentUserId}=>${otherUserId}`, '_replicator');
        await del(`${otherUserId}=>${currentUserId}`, '_replicator');

        console.log({
          caller: 'User / submitHandler / deleted subscription',
          username: subscription.username,
        });
      }
    }
    setProgress(3);

    setInitialValues(values);
    setPreviousSubscriptions(values.subscriptions);
    setIsDirty(false);

    setProgress(-1);
    navigate(`/user/${id}`);
  };

  const cancelHandler = () => {
    navigate(`/user/`);
  };

  const deleteHandler = async () => {
    console.log({
      caller: 'User / deleteHandler',
      id: getValues()?._id,
      props,
    });
    await del(getValues()?._id);
    navigate(`/user/`);
  };

  const validationHandler = async (values: any) => {
    let errors: any = {};
    const credentials = adminCredentials();
    if (!credentials) {
      return errors;
    }
    const { database, username, password } = credentials;
    try {
      const response = await fetch(database, { mode: 'cors' });
      const dbStatus = await response.json();
      if (!dbStatus.couchdb) {
        errors.database = 'The URL is not a couchdb instance';
      }
    } catch (error) {
      errors.database = "Can't access the database";
    }
    if (!!errors.database) {
      return errors;
    }

    const subscriptions = values.subscriptions ?? [];

    for (let i = 0; i < subscriptions.length; i++) {
      let subscription = subscriptions[i];
      if (
        !!subscription.username &&
        !(await userExists(subscription.username))
      ) {
        if (!errors.subscriptions) {
          errors.subscriptions = [];
        }
        errors.subscriptions[i] = { username: "User doesn't exist" };
      }
    }

    if (isNew()) {
      if (await userExists(values.username)) {
        errors.username = 'The user is already existing';
      }
    }
    console.log({ caller: 'Users / validationHandler', values, errors });
    return errors;
  };

  const {
    form,
    data,
    setData,
    setIsDirty,
    setInitialValues,
    reset,
    addField,
    unsetField,
    isDirty,
    isValid,
  } = createForm({
    onSubmit: submitHandler,
    extend: reporter(),
    validate: validationHandler,
    initialValues: getValues(),
  });

  const createUserHandler = async () => {
    console.log({
      caller: 'User / createUserHandler',
      props,
      data: data(),
    });
    //    setOpenUserNamePasswordDialog(true);
  };

  const subscriptions = () => data('subscriptions');

  function removeSubscription(index: number) {
    return () => {
      unsetField(`subscriptions.${index}`);
      setIsDirty(true);
    };
  }

  function addSubscription(index?: number) {
    return () => {
      addField(`subscriptions`, { username: '', direction: '' }, index);
      setIsDirty(true);
    };
  }

  console.log({
    caller: 'User ',
    props,
    values: getValues(),
    data: data(),
  });

  return (
    <>
      <Show when={progress() >= 0}>
        <Progress.Root
          value={progress()}
          minValue={0}
          maxValue={3}
          getValueLabel={({ value, max }) =>
            `${value} of ${max} tasks completed`
          }
          class='progress'
        >
          <div class='progress__label-container'>
            <Progress.Label class='progress__label'>
              Processing...
            </Progress.Label>
            <Progress.ValueLabel class='progress__value-label' />
          </div>
          <Progress.Track class='progress__track'>
            <Progress.Fill class='progress__fill' />
          </Progress.Track>
        </Progress.Root>
      </Show>
      <Show when={progress() < 0}>
        <Show when={!isNew()}>
          <h2>{userId(data('username'), data('database'))}</h2>
        </Show>
        <h3>
          <A href={invitationLink()} target='_blank'>
            Invitation
          </A>
        </h3>
        <form use:form>
          <TextField.Root>
            <TextField.Label>Mail address</TextField.Label>
            <TextField.Input
              type='mail'
              name='mail'
              required={true}
              placeholder='Email address'
            />
            <TextField.ErrorMessage>
              Please provide a valid URL
            </TextField.ErrorMessage>
          </TextField.Root>
          <TextField.Root>
            <TextField.Label>Database</TextField.Label>
            <TextField.Input
              type='url'
              name='database'
              required={true}
              placeholder='Database URL'
              readOnly={true}
            />
          </TextField.Root>
          <TextField.Root>
            <TextField.Label>User name</TextField.Label>
            <TextField.Input
              type='text'
              name='username'
              required={true}
              placeholder='user name'
              readOnly={!isNew()}
            />
          </TextField.Root>
          <TextField.Root>
            <TextField.Label>Password</TextField.Label>
            <TextField.Input
              type='text'
              name='password'
              required={true}
              placeholder='Password'
              autocomplete='off'
            />
          </TextField.Root>
          <table>
            <thead>
              <tr>
                <th>
                  <button type='button' onClick={addSubscription()}>
                    +
                  </button>
                </th>
                <th>User name</th>
              </tr>
            </thead>
            <tbody>
              <Index each={subscriptions()}>
                {(_, index) => (
                  <tr>
                    <td>
                      <button type='button' onClick={removeSubscription(index)}>
                        -
                      </button>
                    </td>
                    <td>
                      <TextField.Root>
                        <TextField.Input
                          type='text'
                          name={`subscriptions.${index}.username`}
                          required={true}
                          placeholder='user name'
                        />
                      </TextField.Root>
                    </td>
                  </tr>
                )}
              </Index>
            </tbody>
          </table>
          <Switch>
            <Match when={!isNew()}>
              <Button.Root type='submit' disabled={!isDirty() || !isValid()}>
                Save
              </Button.Root>
              <Button.Root onclick={deleteHandler}>Delete</Button.Root>
            </Match>
            <Match when={isNew()}>
              <Button.Root type='submit' isDisabled={!isValid()}>
                Create
              </Button.Root>
            </Match>
          </Switch>
          <Button.Root onclick={cancelHandler}>Back</Button.Root>
        </form>
      </Show>
    </>
  );
};

export default User;
