import Parser = require("@yang991178/rss-parser")
import * as db from "../db"
import { rssParser, faviconPromise, ActionStatus, AppThunk } from "../utils"
import { RSSItem, insertItems } from "./item"
import { SourceGroup } from "./group"
import { saveSettings } from "./app"

export class RSSSource {
    sid: number
    url: string
    iconurl: string
    name: string
    description: string
    useProxy: boolean

    constructor(url: string, name: string = null) {
        this.url = url
        this.name = name
        this.useProxy = false
    }

    async fetchMetaData(parser: Parser) {
        let feed = await parser.parseURL(this.url)
        if (!this.name && feed.title) this.name = feed.title.trim()
        this.description = feed.description
        let domain = this.url.split("/").slice(0, 3).join("/")
        let f: string = null
        try {
            f = await faviconPromise(domain)
            if (f === null) f = domain + "/favicon.ico"
            let result = await fetch(f)
            if (result.status == 200 && result.headers.has("Content-Type")
                && result.headers.get("Content-Type").startsWith("image")) {
                this.iconurl = f
            }
        } finally {
            return feed
        }
    }

    private static checkItem(source: RSSSource, item: Parser.Item, db: Nedb<RSSItem>): Promise<RSSItem> {
        return new Promise<RSSItem>((resolve, reject) => {
            let i = new RSSItem(item, source)
            db.findOne({
                source: i.source,
                title: i.title,
                date: i.date
            },
            (err, doc) => {
                if (err) {
                    reject(err)
                } else if (doc === null) {
                    resolve(i)
                } else {
                    resolve(null)
                }
            }) 
        })
    }

    static checkItems(source: RSSSource, items: Parser.Item[], db: Nedb<RSSItem>): Promise<RSSItem[]> {
        return new Promise<RSSItem[]>((resolve, reject) => {
            let p = new Array<Promise<RSSItem>>()
            for (let item of items) {
                p.push(this.checkItem(source, item, db))
            }
            Promise.all(p).then(values => {
                resolve(values.filter(v => v != null))
            }).catch(e => { reject(e) })
        })
    }

    static async fetchItems(source: RSSSource, parser: Parser, db: Nedb<RSSItem>) {
        let feed = await parser.parseURL(source.url)
        return await this.checkItems(source, feed.items, db)
    }
}

export type SourceState = {
    [sid: number]: RSSSource
}

export const INIT_SOURCES = "INIT_SOURCES"
export const ADD_SOURCE = "ADD_SOURCE"
export const UPDATE_SOURCE = "UPDATE_SOURCE"
export const DELETE_SOURCE = "DELETE_SOURCE"

interface InitSourcesAction {
    type: typeof INIT_SOURCES
    status: ActionStatus
    sources?: RSSSource[]
    err?
}

interface AddSourceAction {
    type: typeof ADD_SOURCE
    status: ActionStatus
    batch: boolean
    source?: RSSSource
    err?
}

interface UpdateSourceAction {
    type: typeof UPDATE_SOURCE
    source: RSSSource
}

interface DeleteSourceAction {
    type: typeof DELETE_SOURCE,
    source: RSSSource
}

export type SourceActionTypes = InitSourcesAction | AddSourceAction | UpdateSourceAction | DeleteSourceAction

export function initSourcesRequest(): SourceActionTypes {
    return {
        type: INIT_SOURCES,
        status: ActionStatus.Request
    }
}

export function initSourcesSuccess(sources: RSSSource[]): SourceActionTypes {
    return {
        type: INIT_SOURCES,
        status: ActionStatus.Success,
        sources: sources
    }
}

export function initSourcesFailure(err): SourceActionTypes {
    return {
        type: INIT_SOURCES,
        status: ActionStatus.Failure,
        err: err
    }
}

export function initSources(): AppThunk<Promise<void>> {
    return (dispatch) => {
        dispatch(initSourcesRequest())
        return new Promise<void>((resolve, reject) => {
            db.sdb.find({}).sort({ sid: 1 }).exec((err, docs) => {
                if (err) {
                    dispatch(initSourcesFailure(err))
                    reject(err)
                } else {
                    dispatch(initSourcesSuccess(docs))
                    resolve()
                }
            })
        }) 
    }
}

export function addSourceRequest(batch: boolean): SourceActionTypes {
    return {
        type: ADD_SOURCE,
        batch: batch,
        status: ActionStatus.Request
    }
}

export function addSourceSuccess(source: RSSSource, batch: boolean): SourceActionTypes {
    return {
        type: ADD_SOURCE,
        batch: batch,
        status: ActionStatus.Success,
        source: source
    }
}

export function addSourceFailure(err, batch: boolean): SourceActionTypes {
    return {
        type: ADD_SOURCE,
        batch: batch,
        status: ActionStatus.Failure,
        err: err
    }
}

export function addSource(url: string, name: string = null, batch = false): AppThunk<Promise<number>> {
    return (dispatch, getState) => {
        let app = getState().app
        if (app.sourceInit && !app.fetchingItems) {
            dispatch(addSourceRequest(batch))
            let source = new RSSSource(url, name)
            return source.fetchMetaData(rssParser)
                .then(feed => {
                    let sids = Object.values(getState().sources).map(s => s.sid)
                    source.sid = Math.max(...sids, -1) + 1
                    return new Promise<number>((resolve, reject) => {
                        db.sdb.insert(source, (err) => {
                            if (err) {
                                reject(err)
                            } else {
                                dispatch(addSourceSuccess(source, batch))
                                RSSSource.checkItems(source, feed.items, db.idb)
                                    .then(items => insertItems(items))
                                    .then(items => {
                                        //dispatch(fetchItemsSuccess(items))
                                        SourceGroup.save(getState().groups)
                                        resolve(source.sid)
                                    })
                            }
                        })
                    })
                })
                .catch(e => {
                    console.log(e)
                    dispatch(addSourceFailure(e, batch))
                    return new Promise((_, reject) => { reject(e) })
                })
        }
        return new Promise((_, reject) => { reject("Sources not initialized or fetching items.") })
    }
}

export function updateSourceDone(source: RSSSource): SourceActionTypes {
    return {
        type: UPDATE_SOURCE,
        source: source
    }
}

export function updateSource(source: RSSSource): AppThunk {
    return (dispatch) => {
        db.sdb.update({ sid: source.sid }, { $set: { ...source }}, {}, err => {
            if (!err) {
                dispatch(updateSourceDone(source))
            }
        })
    }
}

export function deleteSourceDone(source: RSSSource): SourceActionTypes {
    return {
        type: DELETE_SOURCE,
        source: source
    }
}

export function deleteSource(source: RSSSource): AppThunk {
    return (dispatch, getState) => {
        dispatch(saveSettings())
        db.idb.remove({ source: source.sid }, { multi: true }, (err) => {
            if (err) {
                console.log(err)
                dispatch(saveSettings())
            } else {
                db.sdb.remove({ sid: source.sid }, {}, (err) => {
                    if (err) {
                        console.log(err)
                        dispatch(saveSettings())
                    } else {
                        dispatch(deleteSourceDone(source))
                        SourceGroup.save(getState().groups)
                        dispatch(saveSettings())
                    }
                })
            }
        })
    }
}

export function sourceReducer(
    state: SourceState = {},
    action: SourceActionTypes
): SourceState {
    switch (action.type) {
        case INIT_SOURCES:
            switch (action.status) {
                case ActionStatus.Success: {
                    let newState: SourceState = {}
                    for (let source of action.sources) {
                        newState[source.sid] = source
                    }
                    return newState
                }
                default: return state
            }
        case ADD_SOURCE:
            switch (action.status) {
                case ActionStatus.Success: return {
                    ...state,
                    [action.source.sid]: action.source
                }
                default: return state
            }
        case UPDATE_SOURCE: return {
            ...state,
            [action.source.sid]: action.source
        }
        case DELETE_SOURCE: {
            delete state[action.source.sid]
            return { ...state }
        }
        default: return state
    }
}