import {useStore} from "@/store/store.js";
import {ohlcStart} from "@/charts/chart-misc.js";
import {LRUCache} from "lru-cache";


// support for Dexorder OHLC data files


function dailyFile(resName) {
    function _filename(symbol, timestamp) {
        const date = new Date(timestamp*1000)
        const year = date.getUTCFullYear()
        const month = ('0'+(date.getUTCMonth() + 1)).slice(-2)
        const day = ('0'+date.getUTCDate()).slice(-2)
        return `${resName}/${year}/${month}/${symbol}-${resName}-${year}${month}${day}.csv`
    }
    return _filename
}

function monthlyFile(resName) {
    function _filename(symbol, timestamp) {
        const date = new Date(timestamp*1000)
        const year = date.getUTCFullYear()
        const month = ('0'+(date.getUTCMonth() + 1)).slice(-2)
        return `${resName}/${year}/${symbol}-${resName}-${year}${month}.csv`
    }
    return _filename
}

function yearlyFile(resName) {
    function _filename(symbol, timestamp) {
        const date = new Date(timestamp*1000)
        const year = date.getUTCFullYear()
        return `${resName}/${symbol}-${resName}-${year}.csv`
    }
    return _filename
}

function singleFile(resName) {
    function _filename(symbol, timestamp) {
        return `${resName}/${symbol}-${resName}.csv`
    }
    return _filename
}


function addDay(timestamp) {
    const date = new Date(timestamp*1000)
    return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + 1) / 1000
}

function addMonth(timestamp) {
    const date = new Date(timestamp*1000)
    const result = Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()) / 1000
    // console.log('addMonth', timestamp, result, new Date(timestamp*1000), new Date(result*1000))
    return result
}

function addYear(timestamp) {
    const date = new Date(timestamp*1000)
    return Date.UTC(date.getUTCFullYear() + 1, date.getUTCMonth(), date.getUTCDate()) / 1000
}

function nextDay(timestamp) {
    const date = new Date(timestamp*1000)
    return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + 1, 0, 0, 0) / 1000
}

function nextMonth(timestamp) {
    const date = new Date(timestamp*1000)
    return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1, 0, 0, 0) / 1000
}

function nextYear(timestamp) {
    const date = new Date(timestamp*1000)
    return Date.UTC(date.getUTCFullYear() + 1, 1, 1, 0, 0, 0) / 1000
}


function never(_timestamp) {
    return Number.MAX_SAFE_INTEGER
}


// noinspection PointlessArithmeticExpressionJS
const resolutions = [
    { seconds:     1 * 60, name:  '1m', tvRes:   '1', filename:   dailyFile( '1m'), add: addDay,   nextStart: nextDay,   },
    { seconds:     3 * 60, name:  '3m', tvRes:   '3', filename:   dailyFile( '3m'), add: addDay,   nextStart: nextDay,   },
    { seconds:     5 * 60, name:  '5m', tvRes:   '5', filename:   dailyFile( '5m'), add: addDay,   nextStart: nextDay,   },
    { seconds:    10 * 60, name: '10m', tvRes:  '10', filename:   dailyFile('10m'), add: addDay,   nextStart: nextDay,   },
    { seconds:    15 * 60, name: '15m', tvRes:  '15', filename:   dailyFile('15m'), add: addDay,   nextStart: nextDay,   },
    { seconds:    30 * 60, name: '30m', tvRes:  '30', filename:   dailyFile('30m'), add: addDay,   nextStart: nextDay,   },
    { seconds:    60 * 60, name:  '1H', tvRes:  '60', filename: monthlyFile( '1H'), add: addMonth, nextStart: nextMonth, },
    { seconds:   120 * 60, name:  '2H', tvRes: '120', filename: monthlyFile( '2H'), add: addMonth, nextStart: nextMonth, },
    { seconds:   240 * 60, name:  '4H', tvRes: '240', filename: monthlyFile( '4H'), add: addMonth, nextStart: nextMonth, },
    { seconds:   480 * 60, name:  '8H', tvRes: '480', filename: monthlyFile( '8H'), add: addMonth, nextStart: nextMonth, },
    { seconds:   720 * 60, name: '12H', tvRes: '720', filename: monthlyFile('12H'), add: addMonth, nextStart: nextMonth, },
    { seconds:  1440 * 60, name:  '1D', tvRes:  '1D', filename:  yearlyFile( '1D'), add: addYear,  nextStart: nextYear,  },
    { seconds:  2880 * 60, name:  '2D', tvRes:  '2D', filename:  yearlyFile( '2D'), add: addYear,  nextStart: nextYear,  },
    { seconds:  4320 * 60, name:  '3D', tvRes:  '3D', filename:  yearlyFile( '3D'), add: addYear,  nextStart: nextYear,  },
    { seconds: 10080 * 60, name:  '1W', tvRes:  '1W', filename:  singleFile( '1W'), add: (x)=>x,   nextStart: never,     },
]

const tvResMap = {}
for (const res of resolutions)
    tvResMap[res.tvRes] = res

const dxoResMap = {}
for (const res of resolutions)
    dxoResMap[res.name] = res


const seriesStarts = {}
const ohlcCache = new LRUCache({max:20,ttl:3600*1000,})


async function getUrl(url) {
    let result = ohlcCache[url]
    if (result)
        return result
    try {
        const response = await fetch(url)
        // console.log('got response', response)
        if (response.ok) {
            result = await response.text()
            ohlcCache[url] = result
            return result
        }
        else
            console.error(`could not fetch ${url}: status ${response.statusText}`)
    }
    catch (e) {
        console.error(`Could not fetch ${url}`, e)
    }
    return ''
}


export async function loadOHLC (symbol, contract, from, to, tvRes) {
    // console.log('loadOHLC', tvRes, new Date(1000*from), new Date(1000*to), symbol, contract);
    let chainId
    let bars = [];
    let inverted = false;
    let baseUrl
    let latest = null  // latest time, price

    function fill(end) {
        const period = res.seconds
        if (latest===null) return
        const [latestTime, price] = latest
        end = ohlcStart(end, period)
        const start = ohlcStart(latestTime+period, period)
        // if (start<end)
        //     console.log('filling', latestTime, price, new Date(start*1000), new Date(end*1000))
        for (let now=start; now < end; now += period ) {
            bars.push({time:now * 1000, open:price, high:price, low:price, close:price})
            latest = [now, price]
        }
    }

    if (symbol.x?.data) {
        baseUrl = symbol.x.data.uri
        contract = symbol.x.data.symbol
        chainId = symbol.x.data.chain
        inverted ^= symbol.x.data.inverted
    }
    else {
        baseUrl = `/ohlc/`
        chainId = useStore().chainId
    }
    baseUrl += `${chainId}/${contract}/`

    const res = tvResMap[tvRes]
    const fetches = []
    if (!(baseUrl in seriesStarts)) {
        try {
            // console.log('getting quote', baseUrl+'quote.csv')
            const response = await getUrl(baseUrl+'quote.csv')
            if (response.length) {
                const [start,end,price] = response.split(',')
                seriesStarts[baseUrl] = parseInt(start)
                // console.log(`Series ${baseUrl} starts at ${new Date(start*1000)}`)
            }
            else {
                console.error(`Bad response while fetching ${baseUrl+'quote.csv'}`)
            }
        }
        catch (e) {
            console.error(e)
        }
    }
    let start = from
    if (baseUrl in seriesStarts)
        start = Math.max(start, seriesStarts[baseUrl])

    const end = res.nextStart(to)
    for(let now = start; now < end; now = res.nextStart(now)) {
        const url = baseUrl + res.filename(contract, now);
        const prom = getUrl(url)
        fetches.push(prom);
    }

    const responses = await Promise.all(fetches)
    let finished = false
    for( let ri=0; !finished && ri<responses.length; ri++ ) {
        let lineNum = 0
        const rows = responses[ri].split('\n')
        for( let rj=0; rj<rows.length; rj++) {
            const line = rows[rj];
            if (line.trim().length === 0)
                continue
            const row = line.split(',')
            let time, open, high, low, close=null
            switch (row.length) {
                case 1:
                    if (row[0].length !== 0)
                        console.log(`Warning: weird nonempty row at OHLC line ${lineNum}: "${line}"`)
                    break
                case 2:
                    time = parseInt(row[0])
                    let price = parseFloat(row[1])
                    if (inverted)
                        price = 1/price
                    open = latest === null ? price : latest[1]
                    high = low = close = price
                    break
                case 3:
                    time = parseInt(row[0])
                    if (time < from || time >= to)
                        break
                    open = parseFloat(row[1])
                    close = parseFloat(row[2])
                    if (inverted) {
                        open = 1/open
                        close = 1/close
                    }
                    high = Math.max(open, close)
                    low = Math.min(open,close)
                    if (latest!==null)
                        open = latest[1]
                    break
                case 5:
                    time = parseInt(row[0])
                    if (time < from || time >= to)
                        break
                    open = parseFloat(row[1])
                    high = parseFloat(row[2])
                    low = parseFloat(row[3])
                    close = parseFloat(row[4])
                    if (inverted) {
                        open = 1/open
                        const h = high
                        high = 1/low
                        low = 1/h
                        close = 1/close
                    }
                    break
                default:
                    console.log(`Warning: could not parse line ${lineNum} of OHLC file:\n${line}`)
                    break
            }
            if (time!==null) {
                if (time >= to) {
                    console.log('time is past end of request:', time)
                    finished = true
                    break
                }
                if ( time >= from ) {
                    if ( latest !== null && time > latest[0] + res.seconds) {
                        fill(time)
                    }
                    const bar = {time: time * 1000, open, high, low, close};
                    bars.push(bar)
                    latest = [time, close]
                }
            }
        }
        // console.log(`processed ${lineNum} lines`)
    }
    // console.log('loadOHLC prefill bars', bars)
    fill(to)
    // console.log('loadOHLC bars', bars)
    return bars
}


export function tvResolutionToPeriodString(res) {
    return tvResMap[res].name
}


export function convertTvResolution(res) {
    return tvResMap[res]
}


export function resForName(name) {
    return dxoResMap[name]
}
