import { LTOApi } from "./api"
import { v4 as uuidv4 } from 'uuid';
import { RefStore } from "../../state/state";
import { debug } from "loglevel";

export const TxnStates = ["PENDING","INFLIGHT","WORKING","ERROR","SUCCESS"] as const

export type TxnState = typeof TxnStates[number]

export interface TxnDetails {
  item_uid: string | "galders"
  count:number
  origin:string
  target:string

  origin_path:string
  target_path:string

  origin_account:string
  target_account:string
}

export interface Envelope<REQ,RESP> {
  req: REQ
  resp: RESP
  state: TxnState
}


export abstract class Order {
  action_id: string
  details?:TxnDetails
  created:Date
  state: TxnState
  constructor(details?:TxnDetails) {
    this.state = "PENDING"
    this.details = details
    this.created = new Date()
    this.action_id = uuidv4();
  }

  mark(t:TxnState) {
    this.state = t
  }

  abstract tick(r:RefStore, api:LTOApi):Promise<any>
  abstract status():string
  abstract progress():[number, number]
  abstract error():string

  abstract order_type:OrderType

  parse(i:any):Order {
    this.action_id = i.action_id
    this.details = i.details
    this.created = new Date(i.created)
    this.state = i.state
    return this
  }
}

export abstract class BasicOrder extends Order {
  stage: number
  err?: string

  constructor(details:TxnDetails) {
    super(details)
    this.stage = 0
  }
  progress():[number,number]{
    return [this.stage, 1]
  }
  status():string {
    return this.state
  }
  error():string {
    return this.err ? this.err : ""
  }
  parse(i:any):BasicOrder {
    this.stage = i.stage
    this.err = i.err
    super.parse(i)
    return this
  }
}
/// start user defined

export const OrderTypes = ["InvalidOrder","BankItem","InternalXfer", "PrivateMarket","MarketMove", "MarketMoveToChar"]
export type OrderType = typeof OrderTypes[number]

export class InvalidOrder extends Order{
  order_type = "InvalidOrder"

  msg:string
  constructor(msg: string){
    super(undefined)
    this.msg = msg
    this.mark("ERROR")
  }
  status():string  {
    return "ERROR"
  }
  progress():[number, number] {
    return [0,0]
  }
  error(): string {
    return this.msg
  }
  async tick(r:RefStore, api:LTOApi):Promise<void> {
    return
  }
  parse(i:any):InvalidOrder {
    super.parse(i)
    this.msg = i.msg
    return this
  }
}

export interface BasicResponse {
  status: number
  data: any
  msg?: string
}


export interface InternalXferRequest {
  item_uid:string
  qty:string
  account:string
  new_char:string
}

export interface InternalXferResponse extends BasicResponse {}

export class InternalXfer extends BasicOrder{
  order_type = "InternalXfer"

  originalRequest:InternalXferRequest
  originalResponse?:InternalXferResponse
  constructor(details:TxnDetails) {
    super(details)
    this.originalRequest =  {
      item_uid: details.item_uid,
      qty: details.count.toString(),
      new_char: details.target,
      account: details.origin,
    }
  }
  async tick(r:RefStore, api:LTOApi):Promise<void> {
    if(this.state !== "PENDING") {
      return
    }
    this.mark("WORKING")
    return api.BankAction<InternalXferRequest, InternalXferResponse>("internal-xfer-item",this.originalRequest)
    .then((x:InternalXferResponse)=>{
      if(x.status == 200){
        this.originalResponse = x
        this.stage = 1
        this.mark("SUCCESS")
        const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]!
        origin_item.item_count = origin_item.item_count - this.details?.count!
      }else{
        throw x.msg
      }
    })
    .catch((e)=>{
      debug("InternalXfer",e)
      this.stage = 1
      this.err = e
      this.mark("ERROR")
    })
  }
  parse(i:any):InternalXfer {
    super.parse(i)
    this.originalRequest = i.originalRequest
    this.originalResponse = i.originalResponse
    return this
  }
}

export interface BankItemRequest {
  item_uid:string
  qty:string
  account:string
}

export interface BankItemResponse extends BasicResponse {}

export class BankItem extends BasicOrder{
  order_type = "BankItem";

  originalRequest:BankItemRequest
  originalResponse?:BankItemResponse
  constructor(details:TxnDetails) {
    super(details)
    this.originalRequest =  {
      item_uid: details.item_uid,
      qty: details.count.toString(),
      account: details.target,
    }
  }
  async tick(r:RefStore, api:LTOApi):Promise<void> {
    if(this.state !== "PENDING" ){
      return
    }
    this.mark("WORKING")
    return api.BankAction<BankItemRequest, BankItemResponse>("bank-item",this.originalRequest)
    .then((x)=>{
      debug("BankItem",x)
      if(x.status == 200){
        this.stage = 1
        this.originalResponse = x
        this.mark("SUCCESS")
        const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]!
        origin_item.item_count = origin_item.item_count - this.details?.count!
      }else {
          throw x.msg ? x.msg : "unknown error"
        }
    })
    .catch((e)=>{
      this.stage = 1
      this.err = e
      this.mark("ERROR")
    })
  }

  parse(i:any):BankItem {
    super.parse(i)
    this.originalRequest = i.originalRequest
    this.originalResponse = i.originalResponse
    return this
  }
}


export interface PrivateMarketRequest {
  item_uid:string
  qty:string
  account:string
  currency:string
  price:number
  private:number
}

export interface PrivateMarketResponse extends BasicResponse {}

export class PrivateMarket extends BasicOrder{
  order_type = "PrivateMarket";

  originalRequest:PrivateMarketRequest
  originalResponse?:PrivateMarketResponse

  listingId?: string
  listingHash?: string

  constructor(details:TxnDetails) {
    super(details)
    this.originalRequest =  {
      item_uid: details.item_uid,
      qty: details.count.toString(),
      account: details.origin_account,
      private: 1,
      currency: "0",
      price: 1,
    }
  }
  async tick(r:RefStore, api:LTOApi):Promise<void> {
    if(this.state !== "PENDING" ){
      return
    }
    this.mark("WORKING")
    return api.BankAction<PrivateMarketRequest, PrivateMarketResponse>("sell-item",this.originalRequest)
    .then((x)=>{
      debug("PrivateMarket",x)
      if(x.status == 200){
        this.stage = 1
        this.originalResponse = x
        this.mark("SUCCESS")
        this.listingId = x.data["listing_id"]
        this.listingHash = x.data["hash"]
        try{
        const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]!
        origin_item.item_count = origin_item.item_count - this.details?.count!
        }catch(e){
        }
      }else {
          throw x.msg ? x.msg : "unknown error"
        }
    })
    .catch((e)=>{
      this.stage = 1
      this.err = e
      this.mark("ERROR")
    })
  }

  parse(i:any):PrivateMarket {
    super.parse(i)
    this.originalRequest = i.originalRequest
    this.originalResponse = i.originalResponse
    this.listingId = i.listingId
    this.listingHash = i.listingHash
    return this
  }
}


export interface MarketMoveRequest {
  listing_id?: string
  qty:string
  account:string
  character: string
}

export interface MarketMoveResponse extends BasicResponse {
  item_uid: string
}


export class MarketMove extends PrivateMarket {
  order_type = "MarketMove";

  moveRequest:MarketMoveRequest
  moveResponse?:MarketMoveResponse

  moveStage:number
  moveState: TxnState

  newUid: string

  constructor(details:TxnDetails) {
    super(details)
    this.moveStage = 0
    this.moveState = "PENDING"
    this.newUid = ""
    this.moveRequest =  {
      qty: details.count.toString(),
      account: details.target_account,
      character: (details.target_path.includes("/")) ? details.target : "0" ,
      listing_id: "", // not initially populated
    }
  }
  async tick(r:RefStore, api:LTOApi):Promise<void> {
    try {
      await super.tick(r, api)
    }catch(e){
      return
    }
    switch(super.status()) {
      case "SUCCESS":
        break;
      case "ERROR":
        this.moveState = "ERROR"
        return
      default:
        return
    }
    if(this.moveState !== "PENDING" ){
      return
    }
    this.moveRequest.listing_id = `${this.listingId}-${this.listingHash}`
    this.moveState = "WORKING"
    return api.BankAction<MarketMoveRequest, MarketMoveResponse>("buy-from-order",this.moveRequest)
    .then((x)=>{
      debug("MarketMove",x)
      this.moveResponse = x
      if(x.status == 200){
        this.moveStage = 1
        this.moveState = "SUCCESS"
        this.newUid = x.item_uid
      }else {
          throw x ? x : "unknown error"
        }
    })
    .catch((e)=>{
      this.moveStage = 1
      this.err = e
      this.moveState = "ERROR"
    })
  }

  progress():[number,number]{
    return [this.stage + this.moveStage, 2]
  }
  status():string {
    return this.moveState
  }

  parse(i:any):MarketMove {
    super.parse(i)
    this.moveRequest = i.moveRequest
    this.moveResponse = i.moveResponse
    this.moveState = i.moveState
    this.moveStage = i.moveStage
    return this
  }
}

export class MarketMoveToChar extends MarketMove {
  order_type = "MarketMoveToChar";

  charRequest:InternalXferRequest
  charResponse?:InternalXferResponse

  charStage:number
  charState: TxnState

  constructor(details:TxnDetails) {
    super(details)
    this.charStage = 0
    this.charState = "PENDING"
    this.charRequest =  {
      item_uid: "",
      qty: details.count.toString(),
      new_char: details.target,
      account: details.target_account,
    }
  }
  async tick(r:RefStore, api:LTOApi):Promise<void> {
    try {
      await super.tick(r, api)
    }catch(e){
      return
    }
    switch(super.status()) {
      case "SUCCESS":
        break;
      case "ERROR":
        this.charState = "ERROR"
        return
      default:
        return
    }
    if(this.charState !== "PENDING" ){
      return
    }
    this.charState = "WORKING"
    this.charRequest.item_uid = this.newUid
    return api.BankAction<InternalXferRequest, InternalXferResponse>("internal-xfer-item",this.charRequest)
    .then((x)=>{
      debug("MarketMoveToChar",x)
      this.charResponse = x
      if(x.status == 200){
        this.charStage = 1
        this.charState = "SUCCESS"
      }else {
          throw x ? x : "unknown error"
        }
    })
    .catch((e)=>{
      this.charStage = 1
      this.err = e
      this.charState = "ERROR"
    })
  }

  progress():[number,number]{
    return [this.stage +this.moveStage+ this.charStage, 3]
  }
  status():string {
    return this.charState
  }

  parse(i:any):MarketMoveToChar {
    super.parse(i)
    this.charRequest = i.charRequest
    this.charResponse = i.charResponse
    this.charState = i.charState
    this.charStage = i.charStage
    return this
  }
}