package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"net"
	"os"
	"strconv"
	"time"

	//"errors"

	"github.com/labstack/gommon/color"
	_ "github.com/lib/pq"
	"gopkg.in/labstack/echo.v1"
	mw "gopkg.in/labstack/echo.v1/middleware"
)

var (
	PORT         = os.Getenv("PORT")
	DATABASE_URL = os.Getenv("DATABASE_URL")
	API_LOG      = os.Getenv("API_LOG")
	FIXED_TOKEN  = os.Getenv("X_Auth_Token")
)

const (
	version = "v1"
	//fixedtoken = "2eefa2912f40a6072d28ff0b571aa820"
	//accesslog = "api_log"
	//connectionString = "user=solboxcs password=thsutleodlQj dbname=cs_ccdb sslmode=disable connect_timeout=1 host=cc-gts.ktsh.co.kr port=9000"
)

type JsonFloat64 float64

func (f JsonFloat64) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf("%0.2f", f)), nil
}

type MessageStruct struct {
	Id      string `json:"id"`
	Message string `json:"message"`
}

type UserStruct struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

type ServiceStruct struct {
	Id      string `json:"id"`
	Service string `json:"service"`
	Pass    string `json:"password"`
	Domin   string `json:"domain"`
}

type TrafficStruct struct {
	Sdate   string      `json:"date"`
	In      JsonFloat64 `json:"in"`
	In_min  JsonFloat64 `json:"in_min"`
	In_max  JsonFloat64 `json:"in_max"`
	In_avg  JsonFloat64 `json:"in_avg,numbers"`
	Out     JsonFloat64 `json:"out"`
	Out_min JsonFloat64 `json:"out_min"`
	Out_max JsonFloat64 `json:"out_max"`
	Out_avg JsonFloat64 `json:"out_avg"`
}

type UsageStruct struct {
	Sdate string `json:"date"`
	Quota uint64 `json:"quota"`
	//Quota_min uint64 `json:"quota_min"`
	//Quota_max uint64 `json:"quota_max"`
	Used     JsonFloat64 `json:"used"`
	Used_min JsonFloat64 `json:"used_min"`
	Used_max JsonFloat64 `json:"used_max"`
}

// Response
type ErrMsgOutput struct {
	Status  string `json:"status"`
	Code    int    `json:"errorcode"`
	Message string `json:"error"`
}

type UserOutput struct {
	Data []UserStruct `json:"data"`
}

type ServiceOutput struct {
	Data []ServiceStruct `json:"data"`
}

type TrafficOutput struct {
	Data []TrafficStruct `json:"data"`
}

type UsageOutput struct {
	Data []UsageStruct `json:"data"`
}

// Handler
func hello(c *echo.Context) error {
	//fmt.Println("token => " + c.Form("token"))
	s := fmt.Sprintf("Hello, World! token = %v\n", c.Form("token"))
	return c.String(http.StatusOK, s)
}

func users(c *echo.Context) error {
	keyword := c.Form("lkey")
	db, err := sql.Open("postgres", DATABASE_URL)
	if err != nil {
		//log.Fatal(err)
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer db.Close()

	var rows *sql.Rows
	if keyword != "" {
		like := "%" + keyword + "%"
		rows, err = db.Query("SELECT c.id, ext.name FROM cs_service.cs_customer c LEFT JOIN cs_service.cs_customer_ext ext ON  ext.user_seq = c.user_seq WHERE c.del_yn = 'N' AND ext.name LIKE $1 ORDER BY c.id;", like)
	} else {
		rows, err = db.Query("SELECT c.id, ext.name FROM cs_service.cs_customer c LEFT JOIN cs_service.cs_customer_ext ext ON  ext.user_seq = c.user_seq WHERE c.del_yn = 'N' ORDER BY c.id;")
	}
	if err != nil {
		//log.Fatal(err)
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer rows.Close()

	users := make([]UserStruct, 0)

	var uid string
	var name string

	for rows.Next() {
		err = rows.Scan(&uid, &name)
		users = append(users, UserStruct{uid, name})
	}

	return c.JSON(http.StatusOK, UserOutput{users})
}

func services(c *echo.Context) error {
	qid := c.Form("id")

	db, err := sql.Open("postgres", DATABASE_URL)
	if err != nil {
		//log.Fatal(err)
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer db.Close()

	res, err := db.Query("SELECT base_domain FROM cs_service.cs_cc_info")
	if err != nil {
		//log.Fatal(err)
		//panic(err)
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer res.Close()

	var base string

	for res.Next() {
		err = res.Scan(&base)

	}
	var rows *sql.Rows

	if qid != "" {
		rows, err = db.Query("SELECT c.id, c.passwd, s.svc_id FROM cs_service.cs_customer c, cs_service.cs_service s WHERE c.user_seq = s.user_seq AND c.id = $1 AND code NOT IN($2) AND s.status_code = $3 ORDER BY c.id", qid, "SVC_TYPE_A_02", "SVC_STATUS_ING")
	} else {
		rows, err = db.Query("SELECT c.id, c.passwd, s.svc_id FROM cs_service.cs_customer c, cs_service.cs_service s WHERE c.user_seq = s.user_seq AND code NOT IN($1) AND s.status_code = $2 ORDER BY c.id", "SVC_TYPE_A_02", "SVC_STATUS_ING")
	}
	if err != nil {
		//log.Fatal(err)
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer rows.Close()

	services := make([]ServiceStruct, 0)
	var uid string
	var passwd string
	var sid string

	for rows.Next() {
		err = rows.Scan(&uid, &passwd, &sid)
		services = append(services, ServiceStruct{uid, sid, passwd, base})
	}

	return c.JSON(http.StatusOK, ServiceOutput{services})
}

func service(c *echo.Context) error {
	//return c.String(http.StatusOK, "Hello, service!!!! svc_id = %v\n", c.Param("svc_id"))
	in := c.Param("svc_id")
	db, err := sql.Open("postgres", DATABASE_URL)
	if err != nil {
		//log.Fatal(err)
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer db.Close()

	res, err := db.Query("SELECT base_domain FROM cs_service.cs_cc_info")
	if err != nil {
		//log.Fatal(err)
		//panic(err)
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer res.Close()

	var base string

	for res.Next() {
		err = res.Scan(&base)
	}

	rows, err := db.Query("SELECT c.id, c.passwd, s.svc_id FROM cs_service.cs_customer c, cs_service.cs_service s WHERE c.user_seq = s.user_seq AND s.svc_id = $1 AND code NOT IN($2) AND s.status_code = $3 ORDER BY c.id", in, "SVC_TYPE_A_02", "SVC_STATUS_ING")
	if err != nil {
		//log.Fatal(err)
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer rows.Close()

	services := make([]ServiceStruct, 0)
	var uid string
	var passwd string
	var sid string

	for rows.Next() {
		err = rows.Scan(&uid, &passwd, &sid)
		services = append(services, ServiceStruct{uid, sid, passwd, base})
	}

	return c.JSON(http.StatusOK, ServiceOutput{services})
}

func traffic(c *echo.Context) error {
	sid := c.Param("svc_id")

	sdata := c.Form("start_date")
	edata := c.Form("end_date")

	now := time.Now()
	if sdata == "" {
		sdata = fmt.Sprintf("%v%v%v", now.Year(), now.Month(), now.Day())
	}

	if edata == "" {
		edata = fmt.Sprintf("%v%v%v", now.Year(), now.Month(), now.Day())
	}

	sdata += " 00:00:00"
	edata += " 23:59:59"

	db, err := sql.Open("postgres", DATABASE_URL)
	if err != nil {
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer db.Close()

	res, err := db.Query("SELECT user_seq, svc_seq FROM cs_service.cs_service WHERE svc_id = $1", sid)
	if err != nil {
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer res.Close()

	var useq uint64
	var sseq uint64
	//fmt.Printf("value %v => %v", sid, useq)
	for res.Next() {
		err = res.Scan(&useq, &sseq)
		//fmt.Printf("value 1 => %v",useq)
	}

	if useq == 0 || sseq == 0 {
		log.Println("Not Found Service.[", sid, "]")
		return echo.NewHTTPError(http.StatusNotFound)
	}

	var rows *sql.Rows

	qstr := `select
			 	b.all_dates reg_date,
			 	round(coalesce(up_traffic_sum, 0), 2) up_traffic_sum,
			 	round(coalesce(up_traffic_min, 0), 2) up_traffic_min,
			 	round(coalesce(up_traffic_max, 0), 2) up_traffic_max,
			 	round(coalesce(up_traffic_avg, 0), 2) up_traffic_avg,
			    round(coalesce(down_traffic_sum, 0), 2) down_traffic_sum,
			 	round(coalesce(down_traffic_min, 0), 2) down_traffic_min,
			 	round(coalesce(down_traffic_max, 0), 2) down_traffic_max,
			 	round(coalesce(down_traffic_avg, 0), 2) down_traffic_avg
		 from (	select
					  distinct to_char(reg_date, 
                            'YYYYmmdd'
					  ) reg_date,
					(coalesce(max(down_traffic), 0) ) down_traffic_max,
					(coalesce(max(up_traffic), 0) ) up_traffic_max,
					(coalesce(min(down_traffic), 0) ) down_traffic_min,
					(coalesce(min(up_traffic), 0) ) up_traffic_min,
					(coalesce(sum(down_traffic), 0) ) down_traffic_sum,
					(coalesce(sum(up_traffic), 0) ) up_traffic_sum,
					(coalesce(avg(down_traffic), 0) ) down_traffic_avg,
					(coalesce(avg(up_traffic), 0) ) up_traffic_avg					
				from cs_stat.cs_stat_network
				where 1 = 1
				  and svc_seq = $1
				  and user_seq = $2
				  -- AND reg_date between to_date($3::text, 'YYYY-mm-dd HH24:MI:SS') AND to_date($4::text, 'YYYY-mm-dd HH24:MI:SS')
				  AND reg_date >= $3 AND reg_date <= $4
				 group by to_char(reg_date, 'YYYYmmdd')
				 order by reg_date
				) a
	        		right join
			        (select a.all_dates
					  from (
							select distinct d1.dates as all_dates
							  from (
									select to_char(to_date($3::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYY') || month || day as dates
									from cs_web.cs_calen_day366
									WHERE LEAP_YN = 'N'
									Union All
									select to_char(to_date($4::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYY') || month || day as dates
									from cs_web.cs_calen_day366
									WHERE LEAP_YN = 'N'
									Union All
									SELECT to_char(to_date( to_char(to_date($3::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYY') || '0301'::text, 'YYYYmmdd') -1, 'YYYYmmdd')
									Union All
									SELECT to_char(to_date( to_char(to_date($4::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYY') || '0301'::text, 'YYYYmmdd') -1, 'YYYYmmdd')
									) d1
							order by d1.dates
							) a
					 where a.all_dates between (to_char(to_date($3::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYYmmdd')) and (to_char(to_date($4::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYYmmdd'))
					 order by a.all_dates
					)b
		on a.reg_date = b.all_dates	
		order by b.all_dates`

	rows, err = db.Query(qstr, sseq, useq, sdata, edata)

	if err != nil {
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer rows.Close()

	traffic := make([]TrafficStruct, 0)

	for rows.Next() {
		var v TrafficStruct
		err = rows.Scan(&v.Sdate,
			&v.In,
			&v.In_min,
			&v.In_max,
			&v.In_avg,
			&v.Out,
			&v.Out_min,
			&v.Out_max,
			&v.Out_avg)

		traffic = append(traffic, v)
	}

	return c.JSON(http.StatusOK, TrafficOutput{traffic})
}

func usage(c *echo.Context) error {
	sid := c.Param("svc_id")

	sdata := c.Form("start_date")
	edata := c.Form("end_date")

	now := time.Now()
	if sdata == "" {
		sdata = fmt.Sprintf("%v%v%v", now.Year(), now.Month(), now.Day())
	}

	if edata == "" {
		edata = fmt.Sprintf("%v%v%v", now.Year(), now.Month(), now.Day())
	}

	sdata += " 00:00:00"
	edata += " 23:59:59"

	db, err := sql.Open("postgres", DATABASE_URL)
	if err != nil {
		//log.Fatal(err)
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer db.Close()

	res, err := db.Query("SELECT user_seq, svc_seq, stg_size FROM cs_service.cs_service WHERE svc_id = $1", sid)
	if err != nil {
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer res.Close()

	var useq uint64
	var sseq uint64
	var quota uint64
	//fmt.Printf("value %v => %v", sid, useq)
	for res.Next() {
		err = res.Scan(&useq, &sseq, &quota)
		//fmt.Printf("value 1 => %v",useq)
	}

	if useq == 0 || sseq == 0 {
		log.Println("Not Found Service.[", sid, "]")
		return echo.NewHTTPError(http.StatusNotFound)
	}

	var rows *sql.Rows

	qstr := `select	b.all_dates	reg_date,
			round(coalesce(used_stg_size_last, 0), 2) used_stg_size_last,
		 	round(coalesce(used_stg_size_max, 0), 2) used_stg_size_max,
		 	round(coalesce(used_stg_size_min, 0), 2) used_stg_size_min
	 from (
			select 
				  to_char(a.reg_date, 
				  'YYYYmmdd'
				  ) reg_date,
					(array_agg(used_stg_size ORDER BY a.reg_date DESC))[1]	used_stg_size_last,
					max(used_stg_size) used_stg_size_max,
					min(used_stg_size) used_stg_size_min
	          from cs_stat.cs_stat_storage a
	         where svc_seq = $1
	           and user_seq = $2
			  -- AND a.reg_date between (to_date($3::text, 'YYYY-mm-dd')) AND to_date($4::text, 'YYYY-mm-dd')
			   AND a.reg_date >= $3 AND a.reg_date <= $4
			  group by to_char(a.reg_date, 'YYYYmmdd')
			) a
	        		right join
			        (select a.all_dates
					  from (
							select distinct d1.dates as all_dates
							  from (
									select to_char(to_date($3::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYY') || month || day as dates
									from cs_web.cs_calen_day366
									WHERE LEAP_YN = 'N'
									Union All
									select to_char(to_date($4::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYY') || month || day as dates
									from cs_web.cs_calen_day366
									WHERE LEAP_YN = 'N'
									Union All
									SELECT to_char(to_date( to_char(to_date($3::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYY') || '0301'::text, 'YYYYmmdd') -1, 'YYYYmmdd')
									Union All
									SELECT to_char(to_date( to_char(to_date($4::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYY') || '0301'::text, 'YYYYmmdd') -1, 'YYYYmmdd')
									) d1
							order by d1.dates
							) a
					 where a.all_dates between (to_char(to_date($3::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYYmmdd')) and (to_char(to_date($4::text, 'YYYY-mm-dd HH24:MI:SS'), 'YYYYmmdd'))
					 order by a.all_dates
					)b
		on a.reg_date = b.all_dates	
		order by b.all_dates`

	rows, err = db.Query(qstr, sseq, useq, sdata, edata)

	if err != nil {
		log.Println(err)
		return echo.NewHTTPError(http.StatusInternalServerError)
	}
	defer rows.Close()

	usage := make([]UsageStruct, 0)
	for rows.Next() {
		var v UsageStruct

		err = rows.Scan(&v.Sdate,
			&v.Used,
			&v.Used_min,
			&v.Used_max)
		v.Quota = quota
		usage = append(usage, v)
	}
	/*
			var v UsageStruct
			usage := make([]UsageStruct,0);

		    for i := 0; i < 10; i++ {
		        v.Sdate = strconv.Itoa(i);
		        i64 := uint64(i)
		        v.Quota = i64
		        //v.Quota_min = i64
		        //v.Quota_max = i64
		        v.Used = i64
		        v.Used_min = i64
		        v.Used_max = i64
		        usage= append(usage,v);
		    }
	*/
	return c.JSON(http.StatusOK, UsageOutput{usage})
}

// Middleware
// auth
func FixedCert() echo.MiddlewareFunc {
	return func(h echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {

			//token := c.Form("token")
			token := c.Request().Header.Get("X-Auth-Token")
			if token != "" && FIXED_TOKEN == token {
				//fmt.Println("token => " + token)
				//log.Println("token => " + token)
				return h(c)
			}
			log.Println("ERROR : missing token.")
			return echo.NewHTTPError(http.StatusUnauthorized)
		}
	}
}

// logger(get gopkg.in/labstack/echo.v0/middleware/logger.go)
func Logger() echo.MiddlewareFunc {
	return func(h echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {
			start := time.Now()
			if err := h(c); err != nil {
				c.Error(err)
			}
			end := time.Now()
            req := c.Request()
			remoteAddr := req.RemoteAddr
			if ip := req.Header.Get(echo.XRealIP); ip != "" {
				remoteAddr = ip
			} else if ip = req.Header.Get(echo.XForwardedFor); ip != "" {
				remoteAddr = ip
			} else {
				remoteAddr, _, _ = net.SplitHostPort(remoteAddr)
			}

			method := c.Request().Method
			path := c.Request().URL.Path
			if path == "" {
				path = "/"
			}
			size := c.Response().Size()

			n := c.Response().Status()
			code := color.Green(n)
			switch {
			case n >= 500:
				code = color.Red(n)
			case n >= 400:
				code = color.Yellow(n)
			case n >= 300:
				code = color.Cyan(n)
			}

			log.Printf("%s %s %s %s %s %d", remoteAddr, method, path, code, end.Sub(start), size)
			return nil
		}
	}
}

// error function

func MyError(err error, c *echo.Context) {
	//fmt.Println("MyError" + c.Form("token"))
	code := http.StatusInternalServerError
	msg := http.StatusText(code)
	if he, ok := err.(*echo.HTTPError); ok {
		code = he.Code()
		msg = he.Error()
	}

	//if code  == http.StatusNotFound  {
	//	c.JSON(http.StatusMethodNotAllowed, ErrMsgOutput{strconv.Itoa(code), 405, msg} )
	//} else {
	c.JSON(code, ErrMsgOutput{strconv.Itoa(code), code, msg})
	//}
}

func main() {
	// Echo instance
	e := echo.New()
	e.SetHTTPErrorHandler(MyError)

	// set log file
	f, err := os.OpenFile(API_LOG, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		//t.Fatalf("error opening file: %v", err)
		fmt.Printf("error opening file: %v", err)
		os.Exit(1)
	}
	defer f.Close()

	log.SetOutput(f)
	e.SetLogOutput(f)

	// Middleware
	e.Use(Logger())
	e.Use(mw.Recover())
	e.Use(FixedCert())
	// Error
	//e.Use(func(*echo.Context) error {
	//        return errors.New("error")
	//})

	// Routes
	e.Get("/", hello)
	//e.Get("/user", users)
	//e.Get("/service", services)

	ver := e.Group("/" + version)
	//dev := e.Group("/" + "dev")

	// Management class
	gm := ver.Group("/" + "management")
	gm.Get("/user", users)
	gms := gm.Group("/" + "service")
	gms.Get("", services)
	gms.Get("/:svc_id", service)

	//  Statistics class
	gs := ver.Group("/" + "statistics")
	//gs := dev.Group("/" + "statistics")
	gss := gs.Group("/" + "service")
	gss.Get("/:svc_id/traffic", traffic)
	gss.Get("/:svc_id/usage", usage)

	// Start server (HTTP)
	log.Printf("Listening on :%s", PORT)
	e.Run(":" + PORT)
	// Start server (HTTPS)
	//e.RunTLS(":1323",  "cert.pem", "key.pem")
}
