import { useCallback, useState } from 'react';

import {
  Button,
  Flex,
  Input,
  Table,
  TableContainer,
  Tbody,
  Td,
  Text,
  Th,
  Thead,
  Tr,
} from '@chakra-ui/react';
import { LuTrash } from 'react-icons/lu';
import { MdCloudUpload } from 'react-icons/md';

import {
  Album,
  AlbumResource,
  Artist,
  ArtistResource,
  Gender,
  GenderResource,
  RestErrorResponse,
  Track,
  TrackResource,
} from '@/back-api';
import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar';
import { FormSelect } from '@/components/form/FormSelect';
import { useFormidable } from '@/components/form/Formidable';
import { PopUpUploadProgress } from '@/components/popup/PopUpUploadProgress';
import { useAlbumService, useOrderedAlbums } from '@/service/Album';
import { useArtistService, useOrderedArtists } from '@/service/Artist';
import { useGenderService, useOrderedGenders } from '@/service/Gender';
import { useServiceContext } from '@/service/ServiceContext';
import { useTrackService } from '@/service/Track';
import { isNullOrUndefined } from '@/utils/validator';

export class ElementList {
  constructor(
    public id?: number,
    public label?: string
  ) {
    // nothing to do.
  }
}
export class FileParsedElement {
  public isSended: boolean = false;
  public nameDetected: boolean = false;
  public trackIdDetected: boolean = false;
  constructor(
    public uniqueId: number,
    public file: File,
    public title: string,
    public artist?: string,
    public album?: string,
    public trackId?: number
  ) {
    console.log(`Unique element: ${uniqueId}`);
    // nothing to do.
  }
}
export class FileFailParsedElement {
  constructor(
    public uniqueId: number,
    public file: File,
    public reason: string
  ) {
    console.log(`Unique element2: ${uniqueId}`);
    // nothing to do.
  }
}

type FormInsertData = {
  genderId?: number;
  artistId?: number;
  titleAlbum?: string;
};

export const AddPage = () => {
  const [parsedElement, setParsedElement] = useState<FileParsedElement[]>([]);
  const [parsedFailedElement, setParsedFailedElement] = useState<
    FileFailParsedElement[] | undefined
  >(undefined);

  const [needSend, setNeedSend] = useState<boolean>(false);

  // list of all files already registered in the bdd to compare with the current list of files.
  const [listFileInBdd, setListFileInBdd] = useState<any[] | undefined>(
    undefined
  );

  const { dataGenders } = useOrderedGenders();
  const { dataArtist } = useOrderedArtists();
  const { dataAlbums } = useOrderedAlbums();
  const { store: storeGender } = useGenderService();
  const { store: storeArtist } = useArtistService();
  const { store: storeAlbum } = useAlbumService();
  const { store: storeTrack } = useTrackService();
  const { session } = useServiceContext();

  const form = useFormidable<FormInsertData>({});

  const updateNeedSend = () => {
    if (parsedElement.length === 0) {
      setNeedSend(false);
      return;
    }
    let tmp = true;
    for (let iii = 0; iii < parsedElement.length; iii++) {
      if (
        isNullOrUndefined(parsedElement[iii].title) ||
        parsedElement[iii].title === ''
      ) {
        tmp = false;
      }
    }
    setNeedSend(tmp);
  };

  const onTitle = (data: FileParsedElement, value: any): void => {
    data.title = value;
    setParsedElement([...parsedElement]);
    updateNeedSend();
  };

  const removeElementFromList = (data: FileParsedElement, value: any): void => {
    const parsedElementTmp = [...parsedElement];
    for (let iii = 0; iii < parsedElementTmp.length; iii++) {
      if (parsedElementTmp[iii] === data) {
        parsedElementTmp.splice(iii, 1);
        break;
      }
    }
    setParsedElement(parsedElementTmp);
    setParsedFailedElement((previous) => [
      ...(previous ?? []),
      new FileFailParsedElement(
        previous?.length ?? 0,
        data.file,
        'Removed by user.'
      ),
    ]);
    updateNeedSend();
  };

  const onTrackId = (data: FileParsedElement, value: any): void => {
    data.trackId = value;
    setParsedElement([...parsedElement]);
    updateNeedSend();
  };

  const clearData = () => {
    setParsedElement([]);
    setParsedFailedElement(undefined);
    setListFileInBdd(undefined);

    setSuggestedArtist(undefined);
    setSuggestedAlbum(undefined);
  };

  const addFileWithMetaData = (file: File, id: number) => {
    // parsedElement: FileParsedElement[] = [];
    let artist: string | undefined = undefined;
    let album: string | undefined = undefined;
    let trackIdNumber: number | undefined = undefined;
    let title: string = '';

    form.restoreValues();

    console.log(`select file ${file.name}`);
    let tmpName = file.name.replace(/[ \t]*-[ \t]*/g, '-');
    //tmpName = tmpName.replace(/_/g, '-');
    //tmpName = tmpName.replace(/--/g, '-');
    console.log(`select file ${tmpName}`);
    const splitElement = tmpName.split('~');
    if (splitElement.length > 1) {
      artist = splitElement[0];
      tmpName = tmpName.substring(artist.length + 1);
    }
    const splitElement2 = tmpName.split('#');
    if (splitElement2.length > 1) {
      album = splitElement2[0];
      tmpName = tmpName.substring(album.length + 1);
    }
    //console.log("ploppppp " + tmpName);
    const splitElement3 = tmpName.split('-');
    if (splitElement3.length > 1) {
      trackIdNumber = parseInt(splitElement3[0], 10);
      tmpName = tmpName.substring(splitElement3[0].length + 1);
    }
    //console.log("KKKppppp " + tmpName);
    //console.log("    ===> " + splitElement3[0]);
    title = tmpName;

    if (trackIdNumber && isNaN(trackIdNumber)) {
      trackIdNumber = undefined;
    }
    // remove extension
    title = title.replace(new RegExp('\\.(webm|WEBM|Webm)'), '');
    let tmp = new FileParsedElement(
      id,
      file,
      title,
      artist,
      album,
      trackIdNumber
    );
    console.log(`==>${JSON.stringify(tmp)}`);
    // add it in the list.
    return tmp;
  };
  const [suggestedArtist, setSuggestedArtist] = useState<string | undefined>(
    undefined
  );
  const [suggestedAlbum, setSuggestedAlbum] = useState<string | undefined>(
    undefined
  );

  const onChangeFile = (value: any): void => {
    clearData();

    const parsedElementTmp: FileParsedElement[] = [];
    const parsedFailedElementTmp: FileFailParsedElement[] = [];

    for (let iii = 0; iii < value.target.files?.length; iii++) {
      parsedElementTmp.push(addFileWithMetaData(value.target.files[iii], iii));
    }
    // check if all global parameters are generic:
    if (parsedElementTmp.length === 0) {
      updateNeedSend();
      return;
    }

    // clean different artist:
    for (let iii = 1; iii < parsedElementTmp.length; iii++) {
      console.log(
        `check artist [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[0].artist} !== ${parsedElementTmp[iii].artist}'`
      );
      if (parsedElementTmp[0].artist !== parsedElementTmp[iii].artist) {
        parsedFailedElementTmp.push(
          new FileFailParsedElement(
            parsedFailedElementTmp.length,
            parsedElementTmp[iii].file,
            'Remove from list due to wrong artist value'
          )
        );
        console.log(
          `Remove from list (!= artist) : [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[iii].file.name}'`
        );
        parsedElementTmp.splice(iii, 1);
        iii--;
      }
    }
    // clean different album:
    for (let iii = 1; iii < parsedElementTmp.length; iii++) {
      console.log(
        `check album [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[0].album} !== ${parsedElementTmp[iii].album}'`
      );
      if (parsedElementTmp[0].album !== parsedElementTmp[iii].album) {
        parsedFailedElementTmp.push(
          new FileFailParsedElement(
            parsedFailedElementTmp.length,
            parsedElementTmp[iii].file,
            'Remove from list due to wrong album value'
          )
        );
        console.log(
          `Remove from list (!= album) : [${iii + 1}/${parsedElementTmp.length}] '${parsedElementTmp[iii].file.name}'`
        );
        parsedElementTmp.splice(iii, 1);
        iii--;
      }
    }
    setParsedElement(parsedElementTmp);
    setParsedFailedElement(parsedFailedElementTmp);
    console.log(`check : ${JSON.stringify(parsedElementTmp[0])}`);

    // find artistId:
    console.log(`try find artist : ${parsedElementTmp[0].artist}`);
    let artistFound = false;
    dataArtist.forEach((data) => {
      if (
        data.name?.toLowerCase() === parsedElementTmp[0].artist?.toLowerCase()
      ) {
        console.log(`    find artist : ${data.id}`);
        form.setValues({ artistId: data.id });
        artistFound = true;
      }
    });
    if (!artistFound) {
      console.log(`    set Suggested artist : ${parsedElementTmp[0].artist}`);
      setSuggestedArtist(parsedElementTmp[0].artist);
    } else {
      setSuggestedArtist(undefined);
    }
    // try to find album
    console.log(`try find album : ${parsedElementTmp[0].album}`);
    let albumFound = false;
    dataAlbums.forEach((data) => {
      if (
        data.name?.toLowerCase() === parsedElementTmp[0].album?.toLowerCase()
      ) {
        console.log(`    find album : ${data.id}`);
        form.setValues({ albumId: data.id });
        albumFound = true;
      }
    });
    if (!albumFound) {
      console.log(`    set Suggested album : ${parsedElementTmp[0].album}`);
      setSuggestedAlbum(parsedElementTmp[0].album);
    }
    updateNeedSend();
  };

  const [indexUpload, setIndexUpload] = useState<number | undefined>(undefined);
  const [listValues, setListValues] = useState<string[]>([]);
  const [currentPosition, setCurrentPosition] = useState<number>(0);
  const [totalPosition, setTotalPosition] = useState<number>(0);
  const [uploadError, setUploadError] = useState<string | undefined>(undefined);
  const [isFinishedUpload, setIsFinishedUpload] = useState<boolean>(false);

  const progressUpload = useCallback(
    (count: number, total: number) => {
      setTotalPosition(total);
      setCurrentPosition(count);
    },
    [setTotalPosition, setCurrentPosition]
  );

  const uploadNext = useCallback(
    (index: number = 0): void => {
      if (parsedElement.length <= index) {
        console.log('end of upload');
        setIsFinishedUpload(true);
        return;
      }
      setIndexUpload(index);
      console.log(
        `Start upload of file:  ${index}: ${parsedElement[index].title}`
      );
      storeTrack
        .update(
          TrackResource.uploadTrack({
            restConfig: session.getRestConfig(),
            data: {
              title: parsedElement[index].title,
              file: parsedElement[index].file,
              albumId: form.values['albumId'] ?? undefined,
              artistId: form.values['artistId'] ?? undefined,
              genderId: form.values['genderId'] ?? undefined,
              trackId: parsedElement[index].trackId ?? undefined,
            },
            callbacks: {
              progressUpload: progressUpload,
            },
          })
        )
        .then((data: Track) => {
          // element sended good
          // Send next ...
          uploadNext(index + 1);
        })
        .catch((error: RestErrorResponse) => {
          // TODO: manage error
          console.log(`element error: ${JSON.stringify(error, null, 2)}`);
          setUploadError(JSON.stringify(error, null, 2));
        });
    },
    [setUploadError, setIndexUpload, storeTrack, parsedElement]
  );

  const sendFile = (): void => {
    console.log(`Send file requested ... ${parsedElement.length}`);
    setUploadError(undefined);
    setIsFinishedUpload(false);
    setListValues(parsedElement.map((element) => element.file.name));
    uploadNext();
  };

  function onUploadAbort(): void {
    setIndexUpload(undefined);
  }

  function OnUploadClose(): void {
    setIndexUpload(undefined);
  }

  const addNewGender = (data: string): Promise<Gender> => {
    return storeGender.update(
      GenderResource.post({
        restConfig: session.getRestConfig(),
        data: {
          name: data,
        },
      })
    );
  };
  const addNewArtist = (data: string): Promise<Artist> => {
    return storeArtist.update(
      ArtistResource.post({
        restConfig: session.getRestConfig(),
        data: {
          name: data,
        },
      })
    );
  };
  const addNewAlbum = (data: string): Promise<Album> => {
    return storeAlbum.update(
      AlbumResource.post({
        restConfig: session.getRestConfig(),
        data: {
          name: data,
        },
      })
    );
  };

  return (
    <>
      <TopBar title="Add new media" />
      <PageLayout>
        <Flex
          direction="column"
          width="80%"
          marginX="auto"
          padding="10px"
          gap="10px"
        >
          <Flex direction="column" width="full">
            <Flex>
              <Text flex={1}>format:</Text>
              <Text flex={4}>
                The format of the media permit to automatic find meta-data:
                <br />
                Artist~album#idTrack-my name of my media.webm
                <br />
                <b>example:</b> Clarika~Moi En Mieux#22-des bulles.webm
              </Text>
            </Flex>
            <Flex>
              <Text flex={1}>Media:</Text>
              <Input
                flex={4}
                type="file"
                placeholder="Select a media file"
                accept=".webm"
                multiple
                onChange={onChangeFile}
              />
            </Flex>
          </Flex>
          {parsedElement && parsedElement.length !== 0 && (
            <>
              <Text fontSize="30px">Meta-data:</Text>
              <FormSelect
                label="Gender"
                form={form}
                variableName="genderId"
                options={dataGenders}
                keyInputValue="name"
                addNewItem={addNewGender}
              />
              <FormSelect
                label="Artist"
                form={form}
                variableName="artistId"
                options={dataArtist}
                keyInputValue="name"
                addNewItem={addNewArtist}
                suggestion={suggestedArtist}
              />
              <FormSelect
                label="Album"
                form={form}
                variableName="albumId"
                options={dataAlbums}
                keyInputValue="name"
                addNewItem={addNewAlbum}
                suggestion={suggestedAlbum}
              />
              <TableContainer>
                <Table
                  variant="striped"
                  colorScheme="teal"
                  background="gray.700"
                >
                  <Thead>
                    <Tr>
                      <Th>track ID</Th>
                      <Th width="full">Title</Th>
                      <Th>actions</Th>
                    </Tr>
                  </Thead>
                  <Tbody>
                    {parsedElement.map((data) => (
                      <Tr key={data.uniqueId}>
                        <Td>
                          <Input
                            type="number"
                            pattern="[0-9]{0-4}"
                            placeholder="e?"
                            value={data.trackId}
                            onChange={(e) => onTrackId(data, e.target.value)}
                            backgroundColor={
                              data.trackIdDetected === true
                                ? 'darkred'
                                : undefined
                            }
                          />
                        </Td>
                        <Td>
                          <Input
                            type="text"
                            placeholder="Name of the Media"
                            value={data.title}
                            onChange={(e) => onTitle(data, e.target.value)}
                            backgroundColor={
                              data.title === '' ? 'darkred' : undefined
                            }
                          />
                          {data.nameDetected === true && (
                            <>
                              <br />
                              <Text as="span" color="@danger">
                                ^^^This title already exist !!!
                              </Text>
                            </>
                          )}
                        </Td>
                        <Td>
                          <Button
                            onClick={(e) =>
                              removeElementFromList(data, e.target)
                            }
                          >
                            <LuTrash /> Remove
                          </Button>
                        </Td>
                      </Tr>
                    ))}
                  </Tbody>
                </Table>
                <Flex marginY="15px">
                  <Button
                    variant="@primary"
                    onClick={sendFile}
                    disabled={!needSend}
                    marginLeft="auto"
                    marginRight="30px"
                  >
                    <MdCloudUpload /> Upload
                  </Button>
                </Flex>
              </TableContainer>
            </>
          )}

          {listFileInBdd && (
            <>
              <TableContainer>
                <Table
                  variant="striped"
                  colorScheme="teal"
                  background="gray.700"
                >
                  <Thead>
                    <Tr>
                      <Th>track ID</Th>
                      <Th width="full">Title</Th>
                      <Th>actions</Th>
                    </Tr>
                  </Thead>
                  <Tbody>
                    {listFileInBdd.map((data) => (
                      <Tr>
                        <Td>
                          <Text
                            color={
                              data.episodeDetected === true ? 'red' : undefined
                            }
                          >
                            {data.trackId}
                          </Text>
                        </Td>
                        <Td>
                          <Text
                            color={
                              data.nameDetected === true ? 'red' : undefined
                            }
                          >
                            {data.title}
                          </Text>
                        </Td>
                        <Td></Td>
                      </Tr>
                    ))}
                  </Tbody>
                </Table>
              </TableContainer>
            </>
          )}

          {parsedFailedElement && (
            <>
              <Text fontSize="30px">Rejected:</Text>
              <TableContainer>
                <Table
                  variant="striped"
                  colorScheme="teal"
                  background="gray.700"
                >
                  <Thead>
                    <Tr>
                      <Th maxWidth="80%">file</Th>
                      <Th>Reason</Th>
                    </Tr>
                  </Thead>
                  <Tbody>
                    {parsedFailedElement.map((data) => (
                      <Tr key={data.uniqueId}>
                        <Td>{data.file.name}</Td>
                        <Td>{data.reason}</Td>
                      </Tr>
                    ))}
                  </Tbody>
                </Table>
              </TableContainer>
            </>
          )}
        </Flex>
        {indexUpload !== undefined && (
          <PopUpUploadProgress
            title="Upload File(s)"
            currentSize={currentPosition}
            totalSize={totalPosition}
            index={indexUpload}
            elements={listValues}
            error={uploadError}
            onAbort={onUploadAbort}
            onClose={OnUploadClose}
            isFinished={isFinishedUpload}
          />
        )}
      </PageLayout>
    </>
  );
};
