import React, { useState, useEffect, useCallback } from "react";
import LeftPanel from "./LeftPanel";
import MiddlePanel from "./MiddlePanel";
import RightPanel from "./RightPanel";
import { generateUUID, slugify } from "../../utils/utils";
import WireframeSDK from "../../utils/WireframeSDK";
import BaasSDK from "../../utils/BaasSDK";
import TreeSDK from "@/utils/TreeSDK";
import Frame from "../../utils/templates/api/Frame";
import lambdaAPIShell from "../../utils/templates/api/lambdaAPIShell";
import { Endpoint } from "../../utils/types/ApiEndpoint";
import {
  makeBaasPostmanSchemaTemplate,
  makePostmanCollection,
} from "../../utils/helpers/endpoints/makePostman";
import { fetchApiTemplate } from "../../utils/templates/api/index";
import { fetchLambdaEndpoint, lambdas } from "@/utils/templates/api/lambda";
import { defaultEndpoints } from "@/utils/templates/api/default";
import { InteractiveButton } from "@/components/InteractiveButton";
import { buildTreeQLEndpoints } from "@/utils/templates/api/treeQL";
import { GlobalContext, showToast } from "@/context/Global";
import { tokenExpireError } from "@/context/Auth";
import { GitIcon, PostmanIcon, ThreeDotsHorizontal } from "@/assets/svgs";
import { DropdownOptions } from "@/components/DropdownOptions";
import { DropdownOption } from "@/components/DropdownOptions/DropdownOption";

let bSdk = new BaasSDK();

const wSdk = new WireframeSDK();

function ApiBuilder({ wireframe, selectedRoute, handleEndpointSave, saveRef }) {
  const { dispatch } = React.useContext(GlobalContext);
  const [endpoints, setEndpoints] = useState([]);
  const [project, setProject] = useState({});
  const [roles, setRoles] = useState([]);
  const [selectedEndpointIndex, setSelectedEndpointIndex] = useState(null);
  const [selectedField, setSelectedField] = useState(null);
  const [isLoading, setIsLoading] = useState({ state: false, target: null });
  const [schema, setSchema] = useState([]);

  useEffect(() => {
    (async function () {
      let project = await getProject();
      await getRoles();
      let wire = await getWireframe();
      let schema = await getSchema(project);

      await setDefaultEndpoints(schema, JSON.parse(wire?.api_config));
    })();
  }, []);

  const setDefaultEndpoints = async (schema, currentEndpoints) => {
    if (!Array.isArray(currentEndpoints)) {
      currentEndpoints = [];
    }
    let treeQLEndpoints = buildTreeQLEndpoints(schema);
    let filteredEndpoints = currentEndpoints
      .filter((s) => s.type != "default")
      .filter((s) => s.type != "treeql");
    const AllEndpoints = [
      ...filteredEndpoints,
      ...defaultEndpoints,
      ...treeQLEndpoints,
    ];

    setEndpoints(AllEndpoints);
    await saveAPI(AllEndpoints);
  };

  const getWireframe = async () => {
    try {
      let res = await new TreeSDK().getOne("wireframe", wireframe?.id, {
        join: "sow",
      });
      return res.model;
    } catch (error) {
      tokenExpireError(dispatch, error.message);
    }
  };
  const getProject = async () => {
    try {
      bSdk.setTable("projects");
      const result = await bSdk.callRestAPI(
        { id: Number(wireframe.project_id) },
        "GET"
      );
      if (!result.error) {
        setProject(result.model);
        return result.model;
      }
    } catch (error) {
      setIsLoading(false);
      tokenExpireError(dispatch, error.message);
    }
  };

  const getRoles = async () => {
    try {
      bSdk.setProjectId(project.slug);
      bSdk.setSecret(project.secret);
      bSdk.setTable("permission");
      const result = await bSdk.callRestAPI({}, "GETALL");
      if (!result.error) {
        let projectRoles = result.list.map((role) => role.role);
        setRoles(projectRoles);
      }
    } catch (error) {
      setIsLoading({ state: false, target: null });
      tokenExpireError(dispatch, error.message);
    }
  };

  const getSchema = async (project) => {
    try {
      bSdk.setProjectId(bSdk._baas_project_id);
      bSdk.setSecret(bSdk._baas_secret);
      const result = await bSdk.getSchemaTables(project.slug);
      let schemaData = Object.entries(result);
      schemaData = schemaData.map((model) => {
        return { model: model[0], columns: model[1] };
      });
      if (!result.error) {
        setSchema(schemaData);
        return schemaData;
      }
    } catch (error) {
      setIsLoading({ state: false, target: null });
      tokenExpireError(
        dispatch,
        error?.response?.data?.message
          ? error?.response?.data?.message
          : error?.message
      );
    }
  };

  const onEndpointAdd = () => {
    const newEndpoints = [...endpoints, Endpoint(generateUUID(), project.slug)];
    setEndpoints(newEndpoints);
    setSelectedEndpointIndex(endpoints[endpoints.length - 1]?.id);
  };

  const onEndpointGroupAdd = (newEndpoints = []) => {
    setEndpoints([...endpoints, ...newEndpoints]);
    setSelectedEndpointIndex(endpoints[endpoints.length - 1]?.id);
  };

  const onEndpointDelete = (id) => {
    setEndpoints(endpoints.filter((e) => e.id != id));
    setSelectedEndpointIndex(null);
  };

  const onEndpointSelect = (endpoint) => {
    setSelectedEndpointIndex(endpoint.id);
    setSelectedField(null);
  };

  const onEndpointUpdate = (field, value) => {
    const newEndpoints = endpoints.map((endpoint) => {
      if (endpoint.id == selectedEndpointIndex) {
        endpoint[field] = value;
        return endpoint;
      }
      return endpoint;
    });

    setEndpoints(newEndpoints);
  };

  const onSelectField = (field) => {
    setSelectedField(field);
  };

  const saveAPI = async (endpoints) => {
    // try {
    //   let result = await wSdk.saveApiConfig(
    //     wireframe.id,
    //     JSON.stringify(endpoints)
    //   );
    //   if (result.error) throw new Error(result);
    //   showToast(dispatch, "Saved");
    // } catch (error) {
    //   alert("Something went wrong");
    // }
  };

  const handleSave = useCallback(() => {
    // transform workflow to endpoint
    // const tempWorkflows = workflows;
    // const defaultConfig = tempWorkflows[0];
    const endpoint = {
      ...selectedRoute,
    };
    handleEndpointSave(endpoint);
  }, [selectedRoute]);

  const handleCommitToRepo = async (e) => {
    e.target.innerHTML = "Commiting";
    let commitContent = "";
    let files = [];
    let customEndpoints = endpoints
      .filter((e) => e.type != "default")
      .filter((e) => e.type != "lambda");

    lambdas.forEach((lambda) => {
      let lambdaEndpoints = endpoints.filter((e) => e.group == lambda);
      if (lambdaEndpoints.length == 0) return;

      let lambdaImports = fetchLambdaEndpoint(lambda)?.imports ?? "";
      let lambdaCommitContent = ``;
      lambdaEndpoints.forEach((endpoint) => {
        lambdaCommitContent += `
          
        //Endpoint
        /*
        ${typeof endpoint.code !== "undefined" ? endpoint.code : ""}
        */

        `;
      });

      lambdaCommitContent = lambdaAPIShell(lambdaCommitContent, lambdaImports);

      files.push({
        path: `lambda/${lambda}.js`,
        content: btoa(lambdaCommitContent),
      });
    });

    customEndpoints.forEach((endpoint) => {
      commitContent += `
      //Description
      /*
      ${typeof endpoint.doc !== "undefined" ? endpoint.doc : ""}
      */

      //Endpoint
      /*
      ${typeof endpoint.code !== "undefined" ? endpoint.code : ""}
      */
      `;
    });
    commitContent = Frame(
      commitContent,
      makeBaasPostmanSchemaTemplate(customEndpoints)
    );
    let postmanCollection = makePostmanCollection(customEndpoints, project);

    files.push({
      path: `${project.slug}PostmanCollection.json`,
      content: btoa(postmanCollection),
    });

    files.push({
      path: `${project.slug}.js`,
      content: btoa(commitContent),
    });

    files.push({
      path: "index.js",
      content: btoa(fetchApiTemplate("index.js")),
    });

    files.push({
      path: "health.js",
      content: btoa(fetchApiTemplate("health.js")),
    });

    customEndpoints.forEach((endpoint) => {
      files.push({
        path: `test/${slugify(endpoint.name)}.test.js`,
        content: btoa(endpoint.unit),
      });
    });

    const branch = prompt("Enter branch to commit");
    try {
      let res = await wSdk.commitFilesToRepo(
        project.slug,
        `${project.slug}_backend`,
        files,
        true,
        branch
      );
      if (!res.error) {
        showToast(dispatch, "Done");
      }
      showToast(dispatch, "Commited to repo");
    } catch (error) {
      showToast(dispatch, "Error Commiting to repo", "error");
    }
    e.target.innerHTML = "Commit to repo";
  };

  // TODO extract to a better location
  const actions = [
    {
      category: "db",
      name: "Get Record by Field",
      inputs: ["Return as", "Model", "Field", "Field Variable Name"],
      async: true,
      template: function (...args) {
        return `
        sdk.setTable('${args[1]}')
        const ${args[0]} = ${this.async && "await"} sdk.get({${args[2]} : ${
          args[3]
        }}) 
      `;
      },
      description: "This API returns a database record by field",
    },
    {
      category: "db",
      name: "Delete Record",
      inputs: ["model", "idVar"],
      async: true,
      template: function (...args) {
        return `
        sdk.setTable('${args[1]}')
        const ${args[0]} = ${this.async && "await"} sdk.delete({}, ${args[1]}) 
      `;
      },
      description: "This API deletes a database record by id",
    },
  ];

  const threeDotsHorOptionsDropdown = [
    {
      name: "Commit To Repo",
      onClick: () => handleCommitToRepo(),
      icon: <GitIcon className="mr-1 h-5 w-5 " />,
    },
    {
      name: "Create Postman doc",
      onClick: () => {},
      icon: <PostmanIcon className="mr-1 h-5 w-5 " />,
    },
  ];

  return (
    <div className="mt-3 bg-[#f9f9f9]">
      <div className="relative flex items-center justify-between bg-white px-8">
        <div className="text-xl font-semibold">API</div>
        <div className=" flex items-center gap-3">
          <div className="flex items-center gap-2 text-sm text-[#8D8D8D]">
            <span>
              {isLoading.state == true && isLoading.target == "save" ? (
                <svg
                  className={`animate animate-spin`}
                  xmlns="http://www.w3.org/2000/svg"
                  width="16"
                  height="16"
                  viewBox="0 0 16 16"
                  fill="none"
                >
                  <path
                    d="M1.3335 6.66667C1.3335 6.66667 2.67015 4.84548 3.75605 3.75883C4.84196 2.67218 6.34256 2 8.00016 2C11.3139 2 14.0002 4.68629 14.0002 8C14.0002 11.3137 11.3139 14 8.00016 14C5.26477 14 2.9569 12.1695 2.23467 9.66667M1.3335 6.66667V2.66667M1.3335 6.66667H5.3335"
                    stroke="#4F46E5"
                    strokeWidth="1.5"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
              ) : (
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  width="19"
                  height="18"
                  viewBox="0 0 19 18"
                  fill="none"
                >
                  <path
                    d="M15.5 4.5L7.25 12.75L3.5 9"
                    stroke="#059669"
                    strokeWidth="1.5"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
              )}
            </span>

            <span>
              {isLoading.state == true && isLoading.target == "save"
                ? "Saving"
                : "Saved!"}
            </span>
          </div>
          <InteractiveButton
            loading={isLoading.state == true && isLoading.target == "save"}
            disabled={isLoading.state == true && isLoading.target == "save"}
            onClick={handleEndpointSave}
            type="button"
            className=" rounded-md bg-primaryBlue px-5 py-2 font-bold text-white "
          >
            {isLoading.state == true && isLoading.target == "save"
              ? "Saving"
              : "Save"}
            &nbsp; Changes
          </InteractiveButton>
          <DropdownOptions
            icon={<ThreeDotsHorizontal className="h-5 w-5 text-[#8D8D8D]" />}
            iconWrapperClass="rounded-md border border-[#E0E0E0] p-2 hover:bg-[#F4F4F4] "
            childrenWrapperClass=""
          >
            {threeDotsHorOptionsDropdown.map((item, index) => (
              <DropdownOption
                key={index}
                icon={item.icon}
                name={item.name}
                onClick={item.onClick}
              />
            ))}
          </DropdownOptions>
        </div>
      </div>
      <div className="flex min-h-screen">
        <LeftPanel
          endpoints={endpoints}
          onEndpointAdd={onEndpointAdd}
          onEndpointGroupAdd={onEndpointGroupAdd}
          onEndpointDelete={onEndpointDelete}
          onEndpointSelect={onEndpointSelect}
          selectedEndpointIndex={selectedEndpointIndex}
        />
        <MiddlePanel
          endpoint={
            selectedEndpointIndex !== null
              ? endpoints.filter((e) => e.id == selectedEndpointIndex)[0]
              : null
          }
          onSelectField={onSelectField}
          selectedField={selectedField}
        />
        <RightPanel
          selectedField={selectedField}
          selectedEndpoint={
            selectedEndpointIndex !== null
              ? endpoints.filter((e) => e.id == selectedEndpointIndex)[0]
              : null
          }
          onFieldChange={(field, value) => onEndpointUpdate(field, value)}
          actions={actions}
          roles={roles}
        />
      </div>
      <button hidden onClick={handleSave} ref={saveRef}>
        save
      </button>
    </div>
  );
}

export default ApiBuilder;
