namespace SqlDynamite.Common

open System
open System.Data
open MongoDB.Driver
open MongoDB.Bson

type MongoDbMetadataFinder() =
    inherit MetadataFinder()

    override this.GetDatabaseList (scname:string) : string list =
        let mongoUrl = MongoUrl(this._connection.ConnectionString)
        let mongoUrlDb = mongoUrl.DatabaseName
        let connStr = if this._connection.ConnectionString.Contains("mongodb.net") && mongoUrlDb <> null then this._connection.ConnectionString.Replace("/" + mongoUrlDb.ToString() + "?ssl=true", "?ssl=true") else this._connection.ConnectionString
        let client = MongoClient(connStr)
        let databaseNames = client.ListDatabaseNames()
        let databasesExist = databaseNames.MoveNext()
        if not databasesExist then raise (Exception("Databases do not exist in the selected cluster"))
        let lst = databaseNames.Current
        List.ofSeq lst

    override this.Compose(tablename:string, fields:DataTable, keys:DataTable, checks:DataTable) : string =
        let mongoUrl = MongoUrl(this._connection.ConnectionString)
        let mongoUrlDb = mongoUrl.DatabaseName
        let connStr = if this._connection.ConnectionString.Contains("mongodb.net") && mongoUrlDb <> null then this._connection.ConnectionString.Replace("/" + mongoUrlDb.ToString() + "?ssl=true", "?ssl=true") else this._connection.ConnectionString
        let client = MongoClient(connStr)
        let parts = tablename.Split([|" : "|], StringSplitOptions.RemoveEmptyEntries)
        let db = parts.[0]
        let col = parts.[1]
        let database = client.GetDatabase(db)
        let collection = database.GetCollection(col)
        
        let bsonDocument = BsonDocument()
        let bsonDocumentFilterDefinition = BsonDocumentFilterDefinition<BsonDocument>(bsonDocument)

        let rec loop (doc:BsonDocument, names:_ list, n, l, acc) =
            if n > 0 then
                let name = names.Item(n - 1)
                let item = doc.GetType().GetProperty("Item", [|typedefof<string>|]).GetValue(doc, [|name|]) :?> BsonValue
                let typ = item.BsonType
                if typ = BsonType.Document then
                    let lst = List.ofSeq((item :?> BsonDocument).Names)
                    loop (doc, names, (n - 1), l, (String.replicate l "\t" + name + " : " + typ.ToString() :: [String.replicate l "\t" + "{"] @ loop ((item :?> BsonDocument), lst, lst.Length, l + 1, [String.replicate l "\t" + "}"])) @ [] @ acc)
                else
                    loop (doc, names, (n - 1), l, (String.replicate l "\t" + name + " : " + typ.ToString() :: acc))
            else
                acc

        let ffb = collection.Find(bsonDocumentFilterDefinition)
        let ffbl = ffb.Limit(Nullable<int>(1))
        let cursor = ffbl.ToCursor()
        let _ = cursor.MoveNext()
        let mongoRecords = List.ofSeq(cursor.Current)
        let fieldList = if mongoRecords.Length > 0 then
                             let names = List.ofSeq(mongoRecords.Item(0).Names)
                             loop (mongoRecords.Item(0), names, names.Length, 0, [])
                        else
                            []
        ("{\n\t" + String.Join(",\n\t", fieldList) + "\n}").Replace("Document,", "Document").Replace("{,", "{")

    override this.GetDefColumns(name:string, objtype:ObjectType) : DataTable list =
        [null; null; null]

    override this.GetNameByTextColumns(searchStr:string, types:ObjectType list, caseSensitive:bool) : string =
        searchStr

    override this.GetTextColumns(name:string, objtype:ObjectType) : string list =
        match objtype with
        | ObjectType.FUNCTION ->    
            let mongoUrl = MongoUrl(this._connection.ConnectionString)
            let mongoUrlDb = mongoUrl.DatabaseName
            let connStr = if this._connection.ConnectionString.Contains("mongodb.net") && mongoUrlDb <> null then this._connection.ConnectionString.Replace("/" + mongoUrlDb.ToString() + "?ssl=true", "?ssl=true") else this._connection.ConnectionString
            let client = MongoClient(connStr)
            let parts = name.Split([|" : "|], StringSplitOptions.RemoveEmptyEntries)
            let db = parts.[0]
            let col = parts.[1]
            let database = client.GetDatabase(db)
            let systemcollection = database.GetCollection("system.js")
            let bsonDocument = BsonDocument()
            let bsonDocumentFilterDefinition = BsonDocumentFilterDefinition<BsonDocument>(bsonDocument)
            let functions = systemcollection.Find(bsonDocumentFilterDefinition)
            let cursor = functions.ToCursor()
            let _ = cursor.MoveNext()
            let funcList = cursor.Current
            [for fn in funcList do
                let fnName = fn.GetValue("_id").ToString()
                if fnName = col then
                    let fnDef = fn.GetValue("value") :?> BsonJavaScript
                    yield fnDef.Code]
        | ObjectType.VIEW ->
            [this.Compose(name, null, null, null)]
        | _ ->
            []

    override this.GenerateNameScript(search:string, types:ObjectType list, caseSensitive:bool) : string =
        search

    override this.GenerateSearchScript(search:string, caseSensitive:bool) : string =
        search

    override this.GetForeignKeyDefinition(constraintName:string) : string =
        raise (NotImplementedException())

    override this.GetPrimaryKeyDefinition(constraintName:string) : string =
        raise (NotImplementedException())

    override this.GetIndexDefinition(constraintName:string) : string =
        let mongoUrl = MongoUrl(this._connection.ConnectionString)
        let mongoUrlDb = mongoUrl.DatabaseName
        let connStr = if this._connection.ConnectionString.Contains("mongodb.net") && mongoUrlDb <> null then this._connection.ConnectionString.Replace("/" + mongoUrlDb.ToString() + "?ssl=true", "?ssl=true") else this._connection.ConnectionString
        let client = MongoClient(connStr)
        let parts = constraintName.Split([|" : "|], StringSplitOptions.RemoveEmptyEntries)
        let db = parts.[0]
        let col = parts.[1]
        let indexName = parts.[2]
        let database = client.GetDatabase(db)
        let collection = database.GetCollection(col)
        let indexes = collection.Indexes
        let indexNames = indexes.List()
        let _ = indexNames.MoveNext()
        let indexNameList = indexNames.Current
        let indexList = [for index in indexNameList do
                             let name = index.GetValue("name").ToString()
                             if name = indexName then
                                 let key = index.GetValue("key") :?> BsonDocument
                                 let elements = key.Elements
                                 for element in elements do yield element.ToString()]
        "{\n\t" + String.Join(",\n\t", indexList) + "\n}"

    override this.GetSequenceDefinition(sequenceName:string) : string =
        raise (NotImplementedException())

    override this.GetSynonymDefinition(synonymName:string) : string =
        raise (NotImplementedException())

    override this.GetTypeDefinition(typeName:string) : string =
        raise (NotImplementedException())

    override this.GetJobDefinition(jobName:string) : string =
        raise (NotImplementedException())

    override this.GetReportDefinition(reportName:string) : string =
        raise (NotImplementedException())

    override this.GetExtendedProcedureDefinition(procName:string) : string =
        raise (NotImplementedException())

    override this.GetExtendedTriggerDefinition(triggerName:string) : string =
        raise (NotImplementedException())
