import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import {
  FiltersMap,
  FormulaMap,
  HiddenMap,
  ValidationMap,
} from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
import { safeParseFloat } from 'utils/index';
import { StockTransfer } from './StockTransfer';
import { TransferItem } from './TransferItem';
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
import { PurchaseInvoice } from 'models/baseModels/PurchaseInvoice/PurchaseInvoice';

export class StockTransferItem extends TransferItem {
  item?: string;
  location?: string;

  unit?: string;
  transferUnit?: string;
  quantity?: number;
  transferQuantity?: number;
  unitConversionFactor?: number;

  itemDiscountAmount?: Money;
  itemDiscountPercent?: number;

  rate?: Money;
  amount?: Money;

  description?: string;
  hsnCode?: number;

  batch?: string;
  serialNumber?: string;

  parentdoc?: StockTransfer;

  get isSales() {
    return this.schemaName === ModelNameEnum.ShipmentItem;
  }

  get isReturn(): boolean {
    return !!this.parentdoc?.isReturn;
  }

  async getItemDiscountAmount(): Promise<Money | undefined> {
    const docData = (await this.fyo.doc.getDoc(
      this.parentSchemaName == ModelNameEnum.Shipment
        ? ModelNameEnum.SalesInvoice
        : ModelNameEnum.PurchaseInvoice,
      this.parentdoc?.backReference
    )) as SalesInvoice | PurchaseInvoice;

    const discountAmount = docData?.items?.find(
      (val) => val.item === this.item
    )?.itemDiscountAmount;

    return discountAmount;
  }

  async getItemDiscountPercent() {
    const docData = (await this.fyo.doc.getDoc(
      this.parentSchemaName == ModelNameEnum.Shipment
        ? ModelNameEnum.SalesInvoice
        : ModelNameEnum.PurchaseInvoice,
      this.parentdoc?.backReference
    )) as SalesInvoice | PurchaseInvoice;

    const discountPercent = docData?.items?.find(
      (val) => val.item === this.item
    )?.itemDiscountPercent;

    return discountPercent;
  }

  formulas: FormulaMap = {
    description: {
      formula: async () =>
        (await this.fyo.getValue(
          'Item',
          this.item as string,
          'description'
        )) as string,
      dependsOn: ['item'],
    },
    unit: {
      formula: async () =>
        (await this.fyo.getValue(
          'Item',
          this.item as string,
          'unit'
        )) as string,
      dependsOn: ['item'],
    },
    transferUnit: {
      formula: async (fieldname) => {
        if (fieldname === 'quantity' || fieldname === 'unit') {
          return this.unit;
        }

        return (await this.fyo.getValue(
          'Item',
          this.item as string,
          'unit'
        )) as string;
      },
      dependsOn: ['item', 'unit'],
    },
    transferQuantity: {
      formula: (fieldname) => {
        if (fieldname === 'quantity' || this.unit === this.transferUnit) {
          return this.quantity;
        }

        return this.transferQuantity;
      },
      dependsOn: ['item', 'quantity'],
    },
    quantity: {
      formula: async (fieldname) => {
        if (!this.item) {
          return this.quantity as number;
        }

        const itemDoc = await this.fyo.doc.getDoc(
          ModelNameEnum.Item,
          this.item
        );
        const unitDoc = itemDoc.getLink('uom');

        let quantity: number = this.quantity ?? 1;

        if (this.isReturn && quantity > 0) {
          quantity *= -1;
        }

        if (!this.isReturn && quantity < 0) {
          quantity *= -1;
        }

        if (fieldname === 'transferQuantity') {
          quantity = this.transferQuantity! * this.unitConversionFactor!;
        }

        if (unitDoc?.isWhole) {
          return Math.round(quantity);
        }

        return safeParseFloat(quantity);
      },
      dependsOn: [
        'quantity',
        'transferQuantity',
        'transferUnit',
        'unitConversionFactor',
        'isReturn',
      ],
    },
    unitConversionFactor: {
      formula: async () => {
        if (this.unit === this.transferUnit) {
          return 1;
        }

        const conversionFactor = await this.fyo.db.getAll(
          ModelNameEnum.UOMConversionItem,
          {
            fields: ['conversionFactor'],
            filters: { parent: this.item! },
          }
        );

        return safeParseFloat(conversionFactor[0]?.conversionFactor ?? 1);
      },
      dependsOn: ['transferUnit'],
    },
    hsnCode: {
      formula: async () =>
        (await this.fyo.getValue(
          'Item',
          this.item as string,
          'hsnCode'
        )) as string,
      dependsOn: ['item'],
    },
    amount: {
      formula: () => {
        return this.rate?.mul(this.quantity ?? 0) ?? this.fyo.pesa(0);
      },
      dependsOn: ['rate', 'quantity'],
    },
    itemDiscountAmount: {
      formula: async () => {
        return await this.getItemDiscountAmount();
      },
      dependsOn: ['items'],
    },
    itemDiscountPercent: {
      formula: () => this.getItemDiscountPercent(),
      dependsOn: ['items'],
    },
    rate: {
      formula: async () => {
        const rate = (await this.fyo.getValue(
          'Item',
          this.item as string,
          'rate'
        )) as undefined | Money;

        if (!rate?.float && this.rate?.float) {
          return this.rate;
        }

        return rate ?? this.fyo.pesa(0);
      },
      dependsOn: ['item'],
    },
    account: {
      formula: () => {
        let accountType = 'expenseAccount';
        if (this.isSales) {
          accountType = 'incomeAccount';
        }
        return this.fyo.getValue('Item', this.item as string, accountType);
      },
      dependsOn: ['item'],
    },
    location: {
      formula: () => {
        if (this.location) {
          return;
        }

        const defaultLocation =
          this.fyo.singles.InventorySettings?.defaultLocation;

        if (defaultLocation && !this.location) {
          return defaultLocation;
        }
      },
    },
  };

  validations: ValidationMap = {
    transferUnit: async (value: DocValue) => {
      if (!this.item) {
        return;
      }

      const item = await this.fyo.db.getAll(ModelNameEnum.UOMConversionItem, {
        fields: ['parent'],
        filters: { uom: value as string, parent: this.item },
      });

      if (item.length < 1)
        throw new ValidationError(
          this.fyo.t`Transfer Unit ${
            value as string
          } is not applicable for Item ${this.item}`
        );
    },
  };

  static filters: FiltersMap = {
    item: (doc: Doc) => {
      let itemNotFor = 'Sales';
      if (doc.isSales) {
        itemNotFor = 'Purchases';
      }

      return { for: ['not in', [itemNotFor]], trackItem: true };
    },
  };

  override hidden: HiddenMap = {
    itemDiscountAmount: () => {
      if (this.itemDiscountAmount && !this.itemDiscountAmount?.isZero()) {
        return false;
      }

      return true;
    },
    itemDiscountPercent: () => !this.itemDiscountPercent,
    batch: () => !this.fyo.singles.InventorySettings?.enableBatches,
    serialNumber: () => !this.fyo.singles.InventorySettings?.enableSerialNumber,
    transferUnit: () =>
      !this.fyo.singles.InventorySettings?.enableUomConversions,
    transferQuantity: () =>
      !this.fyo.singles.InventorySettings?.enableUomConversions,
    unitConversionFactor: () =>
      !this.fyo.singles.InventorySettings?.enableUomConversions,
  };
}
