import {
  IAction,
  IActionResult,
  IActionNode,
  IActionProperty,
  IContext,
} from "../types/ActionTypes";
import { actionAddins } from ".";

interface Props {
  displayName: string;
  sources: Sources;
  condition: Condition;
  verName: VerName;
}

interface Condition {
  queryString: string;
  index: number;
  type: string;
  children: Child[];
  parent: null;
  logicalOperator: string;
}

interface Child {
  index: number;
  type: string;
  parent: string;
  object: Object;
  operator: ChildOperator;
  value: Value;
}

interface Value {
  sharedText: string;
  _t: string;
  objectType: string;
  actionName: string;
  displayName: string;
  name: Name;
}

interface Name {
  name: string;
}

interface ChildOperator {
  label: string;
  value: string;
}

interface Object {
  sharedText: string;
  _t: string;
  name: string;
  objectType: string;
  type: string;
  code: string;
  label: string;
  noLabel: boolean;
  required: boolean;
  minLength: string;
  maxLength: string;
  expression: string;
  hideExpression: boolean;
  unique: boolean;
  defaultValue: string;
}

interface VerName {
  name: string;
  actionName: string;
  displayName: string;
  vars: Var[];
}

interface Var {
  id: string;
  field: Field2;
  logicalOperator: LogicalOperator;
  vName: string;
}

interface LogicalOperator {
  operator: string;
  label: string;
}

interface Field2 {
  sharedText: string;
  _t: string;
  name: string;
  objectType: string;
  type: string;
  code: string;
  label: string;
  noLabel: boolean;
  required: boolean;
  minLength: string;
  maxLength: string;
  expression: string;
  hideExpression: boolean;
  unique: boolean;
  defaultValue: string;
}

interface Sources {
  app: App;
  fields: Field[];
}

interface Field {
  name: string;
  type: string;
  code: string;
  label: string;
  noLabel: boolean;
  required: boolean;
  minLength: string;
  maxLength: string;
  expression: string;
  hideExpression: boolean;
  unique: boolean;
  defaultValue: string;
}

interface App {
  id: string;
  name: string;
  description: string;
  createdate: string;
}

export class DataProcessingAction implements IAction {
  name: string;
  actionProps: IActionProperty[];
  dataProcessingProps: Props | null;
  constructor() {
    this.name = "データ処理";
    this.actionProps = [];
    this.dataProcessingProps = null;
    this.register();
  }

  async process(
    nodes: IActionNode,
    event: any,
    context: IContext
  ): Promise<IActionResult> {
    this.actionProps = nodes.actionProps;
    this.dataProcessingProps = nodes.ActionValue as Props;

    let result = {
      canNext: true,
      result: "",
    } as IActionResult;
    try {
      if (!this.dataProcessingProps) {
        return result;
      }

      const data = await selectData(varGet(this.dataProcessingProps.condition.queryString,context.variables));
      console.log("data ", data);

      context.variables[this.dataProcessingProps.verName.name] =
        this.dataProcessingProps.verName.vars.reduce((acc, f) => {
          const v = calc(f, data);
          if (v) {
            acc[f.vName] = calc(f, data);
          }
          return acc;
        }, {} as AnyObject);

      console.log("context ", context);
      return result;
    } catch (error) {
      console.error(error);
      event.error = error;
      return result;
    }
  }

  register(): void {
    actionAddins[this.name] = this;
  }
}

new DataProcessingAction();

const varGet = (str: string, vars: any) => {
  const regex = /var\((.*?)\)/g;
  let match;
  while ((match = regex.exec(str)) !== null) {
    const varName = match[1];
    if (varName in vars) {
      str = str.replace(match[0], vars[varName]);
    } else {
      throw new Error(`変数${varName}が見つかりません`);
    }
  }
  return str;
};

const selectData = async (query?: string) => {
  return kintone
    .api(kintone.api.url("/k/v1/records", true), "GET", {
      app: kintone.app.getId(),
      query: query,
    })
    .then((resp: Resp) => {
      const result: Result = {};
      resp.records.forEach((element) => {
        for (const [key, value] of Object.entries(element)) {
          if (!result[key]) {
            result[key] = { type: value.type, value: [] };
          }
          result[key].value.push(value.value);
        }
      });
      return result;
    });
};

type Resp = { records: RespRecordType[] };

type RespRecordType = {
  [key: string]: {
    type: string;
    value: any;
  };
};

type Result = {
  [key: string]: {
    type: string;
    value: any[];
  };
};

type AnyObject = {
  [key: string]: any;
};

const ERROR_TYPE = "ERROR_TYPE";

const calc = (f: Var, result: Result) => {
  const type = typeCheck(f.field.type);
  if (!type) {
    return ERROR_TYPE;
  }

  const fun =
    calcFunc[`${type}_${Operator[f.logicalOperator.operator as keyof typeof Operator]}`];
  if (!fun) {
    return ERROR_TYPE;
  }
  const values = result[f.field.code].value;
  if (!values) {
    return null;
  }
  return fun(values);
};

const typeCheck = (type: string) => {
  switch (type) {
    case "RECORD_NUMBER":
    case "NUMBER":
      return CalcType.NUMBER;
    case "SINGLE_LINE_TEXT":
    case "MULTI_LINE_TEXT":
    case "RICH_TEXT":
      return CalcType.STRING;
    case "DATE":
      return CalcType.DATE;
    case "TIME":
      return CalcType.TIME;
    case "DATETIME":
    case "UPDATED_TIME":
      return CalcType.DATETIME;
    default:
      return null;
  }
};

enum Operator {
  SUM = "SUM",
  AVG = "AVG",
  MAX = "MAX",
  MIN = "MIN",
  COUNT = "COUNT",
  FIRST = "FIRST",
}

enum CalcType {
  NUMBER = "number",
  STRING = "string",
  DATE = "date",
  TIME = "time",
  DATETIME = "datetime",
}

const calcFunc: Record<string, (value: string[]) => string | null> = {
  [`${CalcType.NUMBER}_${Operator.COUNT}`]: (value: string[]) =>
    value.length.toString(),
  [`${CalcType.STRING}_${Operator.COUNT}`]: (value: string[]) =>
    value.length.toString(),
  [`${CalcType.DATE}_${Operator.COUNT}`]: (value: string[]) =>
    value.length.toString(),
  [`${CalcType.TIME}_${Operator.COUNT}`]: (value: string[]) =>
    value.length.toString(),
  [`${CalcType.DATETIME}_${Operator.COUNT}`]: (value: string[]) =>
    value.length.toString(),

  [`${CalcType.NUMBER}_${Operator.SUM}`]: (value: string[]) =>
    value.reduce((acc, v) => acc + Number(v), 0).toString(),
  [`${CalcType.NUMBER}_${Operator.AVG}`]: (value: string[]) =>
    (value.reduce((acc, v) => acc + Number(v), 0) / value.length).toString(),
  [`${CalcType.NUMBER}_${Operator.MAX}`]: (value: string[]) =>
    Math.max(...value.map(Number)).toString(),
  [`${CalcType.NUMBER}_${Operator.MIN}`]: (value: string[]) =>
    Math.min(...value.map(Number)).toString(),

  [`${CalcType.STRING}_${Operator.SUM}`]: (value: string[]) => value.join(" "),

  [`${CalcType.DATE}_${Operator.MAX}`]: (value: string[]) =>
    value.reduce((maxDate, currentDate) =>
      maxDate > currentDate ? maxDate : currentDate
    ),

  [`${CalcType.DATE}_${Operator.MIN}`]: (value: string[]) =>
    value.reduce((minDate, currentDate) =>
      minDate < currentDate ? minDate : currentDate
    ),

  [`${CalcType.TIME}_${Operator.MAX}`]: (value: string[]) =>
    value.reduce((maxTime, currentTime) =>
      maxTime > currentTime ? maxTime : currentTime
    ),
  [`${CalcType.TIME}_${Operator.MIN}`]: (value: string[]) =>
    value.reduce((minTime, currentTime) =>
      minTime < currentTime ? minTime : currentTime
    ),

  [`${CalcType.DATETIME}_${Operator.MAX}`]: (value: string[]) =>
    value.reduce((maxDateTime, currentDateTime) =>
      new Date(maxDateTime) > new Date(currentDateTime)
        ? maxDateTime
        : currentDateTime
    ),

  [`${CalcType.DATETIME}_${Operator.MIN}`]: (value: string[]) =>
    value.reduce((minDateTime, currentDateTime) =>
      new Date(minDateTime) < new Date(currentDateTime)
        ? minDateTime
        : currentDateTime
    ),
  [`${CalcType.STRING}_${Operator.FIRST}`]: (value: string[]) => {
    return value[0];
  },
};
