# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import socorro.unittest.testlib.dbtestutil as dbtu

import socorro.lib.psycopghelper as psycopghelper
import socorro.lib.ConfigurationManager as configurationManager
import socorro.database.postgresql as db_postgresql
import socorro.database.schema as db_schema
import socorro.database.database as sdatabase

from socorro.lib.datetimeutil import UTC, utc_now

from nose.tools import *
from socorro.unittest.testlib.testDB import TestDB
import libTestconfig as testConfig
import socorro.unittest.testlib.createJsonDumpStore as createJDS
import socorro.unittest.testlib.util as tutil

import psycopg2

import datetime as dt
import errno
import logging
import os
import re
import time

logger = logging.getLogger()

class Me:  pass
me = None

def setup_module():
  global me
  if me:
    return
  me = Me()
  me.config = configurationManager.newConfiguration(configurationModule = testConfig, applicationName='Testing dbtestutil')
  tutil.nosePrintModule(__file__)
  myDir = os.path.split(__file__)[0]
  if not myDir: myDir = '.'
  replDict = {'testDir':'%s'%myDir}
  for i in me.config:
    try:
      me.config[i] = me.config.get(i)%(replDict)
    except:
      pass

  logger.setLevel(logging.DEBUG)
  logFilePathname = me.config.logFilePathname
  logfileDir = os.path.split(me.config.logFilePathname)[0]
  try:
    os.makedirs(logfileDir)
  except OSError,x:
    if errno.EEXIST != x.errno: raise
  f = open(me.config.logFilePathname,'w')
  f.close()

  fileLog = logging.FileHandler(logFilePathname, 'a')
  fileLog.setLevel(logging.DEBUG)
  fileLogFormatter = logging.Formatter(me.config.logFileLineFormatString)
  fileLog.setFormatter(fileLogFormatter)
  logger.addHandler(fileLog)
  me.database = sdatabase.Database(me.config)
  me.connection = me.database.connection()
  #me.dsn = "host=%(databaseHost)s dbname=%(databaseName)s user=%(databaseUserName)s password=%(databasePassword)s" % (me.config)
  #me.connection = psycopg2.connect(me.dsn)
  me.testDB = TestDB()
  me.testDB.removeDB(me.config,logger)
  me.testDB.createDB(me.config,logger)

def teardown_module():
  me.testDB.removeDB(me.config,logger)
  me.connection.close()
  if os.path.isfile(me.config.logFilePathname):
    os.remove(me.config.logFilePathname)

# this was a bad test in that it relies on the datetime in the database to be
# in sync with the datetime on the test machine
#def testDatetimeNow():
  #global me
  #cursor = me.connection.cursor()
  #before = dt.datetime.now()
  #time.sleep(.01)
  #got = dbtu.datetimeNow(cursor)
  #time.sleep(.01)
  #after = dt.datetime.now()
  #assert before < got and got < after, "but\nbefore:%s\ngot:   %s\nafter: %s"%(before,got,after)

def testFillProcessorTable_NoMap():
  """ testDbtestutil:testFillProcessorTable_NoMap():
  - check correct behavior for presence or absence of parameter 'stamp'
  - check correct number of entries created
  - check correct number of priority_job_X tables created
  """
  global me
  cursor = me.connection.cursor()
  ssql = "SELECT id,name,startdatetime,lastseendatetime FROM processors"
  dsql = "DELETE FROM processors"
  dropSql = "DROP TABLE IF EXISTS %s"
  stamps = [None,None,dt.datetime(2008,1,2,3,4,5,666,tzinfo=UTC),dt.datetime(2009,1,2,3,tzinfo=UTC), None, dt.datetime(2010,12,11,10,9,8,777,tzinfo=UTC)]
  try:
    for i in range(len(stamps)):
      before = utc_now()
      time.sleep(.01)
      dbtu.fillProcessorTable(cursor,i,stamp=stamps[i])
      time.sleep(.01)
      after =  utc_now()
      cursor.execute(ssql)
      data = cursor.fetchall()
      assert i == len(data)
      for d in data:
        if stamps[i]:
          assert stamps[i] == d[2]
          assert stamps[i] == d[3]
        else:
          assert before < d[2] and d[2] < after
          assert d[2] == d[3]
      priJobsTables = db_postgresql.tablesMatchingPattern("priority_jobs_%",cursor)
      assert i == len(priJobsTables)
      cursor.execute(dsql)
      if priJobsTables:
        cursor.execute(dropSql%(','.join(priJobsTables)))
      me.connection.commit()
  finally:
    pt = db_schema.ProcessorsTable(logger)
    pt.drop(cursor)
    pt.create(cursor)
    cursor.execute('DELETE FROM jobs')
    me.connection.commit()

def testFillProcessorTable_WithMap():
  """testDbtestutil:testFillProcessorTable_WithMap():
  - check that othr params ignored for non-empty map
  - check that mapped data is used correctly (id is ignored, mapped stamp is lastseendatetime)
  """
  global me
  cursor = me.connection.cursor()
  ssql = "SELECT id,name,startdatetime,lastseendatetime FROM processors"
  dsql = "DELETE FROM processors"
  dropSql = "DROP TABLE IF EXISTS %s"
  tmap = {12:dt.datetime(2008,3,4,5,6,12,tzinfo=UTC),37:dt.datetime(2009,5,6,7,8,37,tzinfo=UTC)}
  try:
    dbtu.fillProcessorTable(cursor,7,stamp=dt.datetime(2009,4,5,6,7,tzinfo=UTC),processorMap=tmap)
    cursor.execute(ssql)
    data = cursor.fetchall()
    me.connection.commit()
    assert 2 == len(data)
    expectSet = set([dt.datetime(2008,3,4,5,6,12,tzinfo=UTC),dt.datetime(2009,5,6,7,8,37,tzinfo=UTC)])
    gotSet = set()
    for d in data:
      assert dt.datetime(2009,4,5,6,7,tzinfo=UTC) == d[2]
      gotSet.add(d[3])
      assert d[0] in [1,2]
    assert expectSet == gotSet
  finally:
    pt = db_schema.ProcessorsTable(logger)
    pt.drop(cursor)
    pt.create(cursor)
    cursor.execute('DELETE FROM jobs')
    me.connection.commit()

def testMoreUuid():
  m = {'hexD':'[0-9a-fA-F]'}
  p = '^%(hexD)s{8}-%(hexD)s{4}-%(hexD)s{4}-%(hexD)s{4}-%(hexD)s{12}$'%m
  rep = re.compile(p)
  gen = dbtu.moreUuid()
  seen = set()
  # surely no test set has more than 150K uuids... and we want to run in < 1 second
  for i in range(150000):
    d = gen.next()
    assert 36 == len(d)
    assert d not in seen
    assert rep.match(d)
    seen.add(d)

def _makeJobDetails(aMap):
  "This is a test, but it is also a setup for the next test, so it will run there, not alone"
  jdCount = {1:0,2:0,3:0,4:0}
  data = dbtu.makeJobDetails(aMap)
  for d in data:
    jdCount[d[2]] += 1
    assert '/' in d[0]
    assert d[1] in d[0]
  assert jdCount == aMap
  return data

def testAddSomeJobs():
  global me
  cursor = me.connection.cursor()
  cursor.execute("SELECT id from processors")
  me.connection.commit()
  jdMap = {1:1,2:2,3:3,4:0}
  xdMap = {1:set(),2:set(),3:set(),4:set()}
  gdMap = {1:set(),2:set(),3:set(),4:set()}
  data = _makeJobDetails(jdMap)
  for d in data:
    xdMap[d[2]].add(d)
  try:
    dbtu.fillProcessorTable(cursor,3,logger=logger)
    cursor.execute("SELECT id from processors")
    me.connection.commit()
    addedJobs = dbtu.addSomeJobs(cursor,jdMap)
    me.connection.commit()
    assert data == addedJobs
    cursor.execute("SELECT pathname,uuid,owner FROM jobs ORDER BY OWNER ASC")
    me.connection.commit()
    data2 = cursor.fetchall()
    assert len(data) == len(data2)
    for d in data2:
      gdMap[d[2]].add(d)
    assert xdMap == gdMap
  finally:
    pt = db_schema.ProcessorsTable(logger)
    pt.drop(cursor)
    pt.create(cursor)
    cursor.execute("DELETE from jobs")
    me.connection.commit()

def testSetPriority_Jobs():
  global me
  cursor = me.connection.cursor()
  try:
    dbtu.fillProcessorTable(cursor,3,stamp=dt.datetime(2008,3,4,5,6,7,tzinfo=UTC))
    cursor.execute("SELECT id FROM processors")
    me.connection.commit()
    counts = dict((x[0],x[0]) for x in cursor.fetchall())
    dbtu.addSomeJobs(cursor,counts,logger)
    cursor.execute("SELECT id FROM jobs")
    me.connection.commit()
    jobIds = [x[0] for x in cursor.fetchall()]
    half = len(jobIds)/2
    expectPri = jobIds[:half]
    expectNon = jobIds[half:]
    dbtu.setPriority(cursor,expectPri)
    cursor.execute("SELECT id FROM jobs WHERE priority > 0 ORDER BY id")
    gotPri = [x[0] for x in cursor.fetchall()]
    cursor.execute("SELECT id FROM jobs WHERE priority = 0 ORDER BY id")
    gotNon = [x[0] for x in cursor.fetchall()]
    me.connection.commit()
    assert expectPri == gotPri
    assert expectNon == gotNon
  finally:
    jt = db_schema.JobsTable(logger)
    jt.drop(cursor)
    jt.create(cursor)
    pt = db_schema.ProcessorsTable(logger)
    pt.drop(cursor)
    pt.create(cursor)
    me.connection.commit()

def testSetPriority_PriorityJobs():
  global me
  cursor = me.connection.cursor()
  try:
    dbtu.fillProcessorTable(cursor,3,stamp=dt.datetime(2008,3,4,5,6,7,tzinfo=UTC))
    cursor.execute("SELECT id FROM processors")
    counts = dict((x[0],x[0]) for x in cursor.fetchall())
    dbtu.addSomeJobs(cursor,counts,logger)
    cursor.execute("SELECT id,uuid FROM jobs")
    me.connection.commit()
    data = cursor.fetchall()
    jobIds = [x[0] for x in data]
    jobUuids = [x[1] for x in data]
    half = len(jobIds)/2
    expect1Pri = jobIds[:half]
    expect2Pri = jobIds[half:]
    expect1Uuid = sorted(jobUuids[:half])
    expect2Uuid = sorted(jobUuids[half:])
    dbtu.setPriority(cursor,expect1Pri,'priority_jobs_1')
    dbtu.setPriority(cursor,expect2Pri,'priority_jobs_2')
    sql = "SELECT uuid from %s ORDER BY uuid"
    cursor.execute(sql%'priority_jobs_1')
    got1Uuid = [x[0] for x in cursor.fetchall()]
    cursor.execute(sql%'priority_jobs_2')
    got2Uuid = [x[0] for x in cursor.fetchall()]
    me.connection.commit()
    assert expect1Uuid == got1Uuid
    assert expect2Uuid == got2Uuid
  finally:
    jt = db_schema.JobsTable(logger)
    jt.drop(cursor)
    jt.create(cursor)
    pt = db_schema.ProcessorsTable(logger)
    pt.drop(cursor)
    pt.create(cursor)
    me.connection.commit()

def testMoreUrl():
  global me
  noneGen = dbtu.moreUrl(False)
  for i in range(100):
    assert None == noneGen.next()

  allGen = dbtu.moreUrl(True,0)
  setAll = set()
  for i in range(40000):
    setAll.add(allGen.next())
  assert 2001 > len(setAll)
  assert 1998 < len(setAll)

  someGen5 = dbtu.moreUrl(True,5)
  set5 = set()
  for i in range(100):
    set5.add(someGen5.next())
  assert 5 >= len(set5)

  someGen100 = dbtu.moreUrl(True,100)
  set100 = set()
  for i in range(500):
    set100.add(someGen100.next())
  assert 100 >= len(set100)

  tooGen = dbtu.moreUrl(True,40000)
  setToo = set()
  for i in range(40000):
    setToo.add(tooGen.next())
  assert setToo == setAll
