package controllers

import (
	"encoding/csv"
	"errors"
	"fmt"
	"github.com/gofiber/fiber/v2"
	strftime "github.com/itchyny/timefmt-go"
	"github.com/jung-kurt/gofpdf"
	"github.com/leekchan/accounting"
	"gorm.io/gorm"
	"log"
	"math"
	"os"
	"strconv"
	"time"
	"zatobay_api/database"
	"zatobay_api/models"
	"zatobay_api/utils/datatable"
)

type FilterAjaxOrderParkir struct {
	TarifId   int    `json:"category_id" url:"category_id" query:"category_id"`
	NamaAdmin string `json:"nama_admin" url:"nama_admin" query:"nama_admin"`
	StartDate string `json:"start_date" url:"start_date" query:"start_date"`
	EndDate   string `json:"end_date" url:"end_date" query:"end_date"`
	Page      string `json:"page" url:"page" query:"page"`
	PerPage   string `json:"length" url:"length" query:"length"`
	Sort      string `json:"column" url:"column" query:"column"`
	SortDir   string `json:"dir" url:"dir" query:"dir"`
	Draw      string `json:"draw" url:"draw" query:"draw"`
}

type MetaInfo struct {
	TotalIncome int64 `json:"total_income"`
	TotalTrx    int64 `json:"total_trx"`
}

func CreateOrderParkir(c *fiber.Ctx) error {
	var nama_kasir string

	if c.Get("Authorization") == "" {
		return c.JSON(fiber.Map{
			"message": "Auth Token must be set",
		})
	}

	err, id_kasir, nama_kasir := GetNamaKasir(c.Get("Authorization"))
	_, err_id_kasir := strconv.Atoi(id_kasir)

	if err_id_kasir != nil {
		return c.Status(400).JSON(fiber.Map{
			"status": "cannot convert cashier id",
		})
	}

	if err != nil {
		return c.Status(400).JSON(fiber.Map{
			"status": "Error get cashier name",
		})
	}

	if err_id_kasir != nil {
		return err_id_kasir
	}

	data := new(models.OrderParkir)

	if err := c.BodyParser(&data); err != nil {
		return c.Status(400).JSON(err.Error())
	}

	tarif := new(models.TarifParkir)

	resultTarif := database.DB.First(&tarif, data.TarifId)
	if resultTarif.Error != nil {
		return c.Status(400).JSON(fiber.Map{
			"status": "error, tarif not found",
			"error":  resultTarif.Error,
		})
	}

	if data.IsBulk == 1 {
		data.PriceTotal = data.Quantity * tarif.Price
	} else {
		data.PriceTotal = tarif.Price
	}

	data.Tarif = *tarif
	data.NamaAdmin = nama_kasir

	res := database.DB.Create(&data)

	if res.Error != nil {
		return c.Status(400).JSON(fiber.Map{
			"status": "error",
			"error":  res.Error,
		})
	}
	return c.Status(200).JSON(fiber.Map{
		"status": "success create order parkir",
		"data":   data.ToResponse(),
	})

}

func OrderParkirAjaxList(c *fiber.Ctx) error {

	filterAjax := new(FilterAjaxOrderParkir)

	if err := c.QueryParser(filterAjax); err != nil {
		return c.Status(400).JSON(err.Error())
	}

	orderParkir := models.OrderParkir{
		TarifId: filterAjax.TarifId,
	}

	var start, end string

	if filterAjax.StartDate != "" && filterAjax.EndDate != "" {
		start = filterAjax.StartDate + " 00:00:00"
		end = filterAjax.EndDate + " 23:59:59"
	}

	fmt.Println("start" + start)
	fmt.Println(end)

	datatableFormat, err := makeFilter(c, filterAjax, start, end)
	if err != nil {
		return c.Status(400).JSON(err.Error())
	}

	infos := makeMetaInfo(orderParkir, start, end)

	fmt.Print(infos)

	return c.JSON(fiber.Map{
		"message": "success",
		"info":    infos,
		"data":    datatableFormat,
	})
}

func OrderParkirQueryAjaxList(filter models.OrderParkir, limit, offset int, sort map[string]string, start string, end string) ([]models.OrderParkir, *int64, error) {
	var (
		result []models.OrderParkir
		count  int64

		query, dbCount *gorm.DB
	)

	if start == "" {
		query = database.DB.Preload("Tarif").Where(filter)
	} else {
		query = database.DB.Preload("Tarif").Where(filter).Where("created_at BETWEEN ? AND ?", start, end)
	}

	if sort == nil {
		query = query.Order("id")
	} else {
		for key, val := range sort {
			query = query.Order(fmt.Sprintf("%s %s", key, val))
		}
	}
	query = query.Offset(offset).Limit(limit).Find(&result)
	fmt.Println(query)
	if query.Error != nil {
		return nil, nil, query.Error
	}
	if start == "" {
		dbCount = database.DB.Table("order_parkirs").Where(filter).Select("id").Count(&count)
	} else {
		dbCount = database.DB.Table("order_parkirs").Where(filter).Where("created_at BETWEEN ? AND ?", start, end).Select("id").Count(&count)
	}

	if dbCount.Error != nil {
		return nil, nil, dbCount.Error
	}
	return result, &count, nil

}

func OrderParkirAjaxListCashierDaily(c *fiber.Ctx) error {

	filterAjax := new(FilterAjaxOrderParkir)

	if err := c.QueryParser(filterAjax); err != nil {
		return c.Status(400).JSON(err.Error())
	}

	var start, end string

	t := time.Now()
	curr_time := strftime.Format(t, "%Y-%m-%d")
	start = curr_time + " 00:00:00"
	end = curr_time + " 23:59:59"

	datatableFormat, err := makeFilter(c, filterAjax, start, end)
	if err != nil {
		return c.Status(400).JSON(err.Error())
	}

	orderParkir := models.OrderParkir{
		TarifId: filterAjax.TarifId,
	}

	infos := makeMetaInfo(orderParkir, start, end)

	fmt.Print(infos)

	return c.JSON(fiber.Map{
		"message": "success",
		"info":    infos,
		"data":    datatableFormat,
	})
}

func makeFilter(c *fiber.Ctx, filterAjax *FilterAjaxOrderParkir, start string, end string) (*datatable.DataTable, error) {
	page, err := strconv.Atoi(filterAjax.Page)
	if err != nil {
		page = 1
	}
	perPage, err := strconv.Atoi(filterAjax.PerPage)
	if err != nil {
		perPage = 10
	}

	orderParkir := models.OrderParkir{
		TarifId: filterAjax.TarifId,
	}

	var sort map[string]string
	sort = make(map[string]string)
	if filterAjax.Sort != "" {
		if filterAjax.SortDir == "" {
			sort[filterAjax.Sort] = "desc"
		} else {
			sort[filterAjax.Sort] = filterAjax.SortDir
		}
	}
	limit := perPage
	offset := (page - 1) * limit

	data, total, err := OrderParkirQueryAjaxList(orderParkir, limit, offset, sort, start, end)
	fmt.Println(data)

	if err != nil {
		return nil, c.Status(400).JSON(err.Error())
	}
	pages := math.Ceil(float64(*total) / float64(perPage))
	meta := datatable.MetaDataTable{
		CurrentPage: page,
		From:        offset + 1,
		LastPage:    int(pages),
		Path:        "",
		PerPage:     fmt.Sprintf("%v", limit),
		To:          offset + limit,
		Total:       int(*total),
	}

	datatableFormat := datatable.DataTable{
		Meta: datatable.MetaDataTable{
			CurrentPage: page,
			From:        0,
			LastPage:    1,
			Path:        *datatable.BuildUrlForLink(c.Request(), 0),
			PerPage:     strconv.Itoa(perPage),
			To:          0,
			Total:       0,
		},
		Data: nil,
		Links: datatable.LinkDatatable{
			First: *datatable.BuildUrlForLink(c.Request(), 1),
			Last:  nil,
			Next:  nil,
			Prev:  nil,
		},
		Payload: filterAjax,
	}

	if err == nil {
		var dataResponseOrderParkir []models.ResponseOrderParkir

		for _, data := range data {
			dataResponseOrderParkir = append(dataResponseOrderParkir, *data.ToResponse())
		}

		datatableFormat.Data = dataResponseOrderParkir
		meta.Path = *datatable.BuildUrlForLink(c.Request(), 0)
		datatableFormat.Meta = meta
		datatableFormat.Links = datatable.BuildLinksData(c.Request(), meta)
	}
	return &datatableFormat, nil
}

func makeMetaInfo(orderParkir models.OrderParkir, start string, end string) MetaInfo {

	var infos MetaInfo

	if start == "" {
		database.DB.Table("order_parkirs").Select("sum(cast(price_total as int)) as total_income, count(*) as total_trx").Where(orderParkir).Scan(&infos)
	} else {
		database.DB.Table("order_parkirs").Select("sum(cast(price_total as int)) as total_income, count(*) as total_trx").Where(orderParkir).Where("order_parkirs.created_at BETWEEN ? AND ?", start, end).Scan(&infos)
	}
	return infos
}

func GetOrderParkirById(c *fiber.Ctx) error {

	var data models.OrderParkir

	paramsId := c.Params("id")

	id_int, err := strconv.Atoi(paramsId)

	if err != nil {
		return err
	}

	result := database.DB.Preload("Tarif").First(&data, id_int)

	if result.RowsAffected == 0 {
		return c.SendStatus(404)
	}

	return c.Status(200).JSON(fiber.Map{
		"message": "success",
		"data":    data.ToResponse(),
	})
}

func DeleteOrderParkirById(c *fiber.Ctx) error {

	id := c.Params("id")
	id_int, err := strconv.Atoi(id)

	if err != nil {
		id_int = -1
	}

	var data models.OrderParkir

	result := database.DB.Delete(&data, id_int)

	if result.RowsAffected == 0 {
		return c.SendStatus(404)
	}

	return c.Status(200).JSON(fiber.Map{
		"message": "success delete data, id :" + id,
		"status":  "success",
	})
}

func PrintPdfOrderParkir(c *fiber.Ctx) error {

	// filter := new(FilterDateRange)
	filter := new(FilterAjaxOrderParkir)

	if err := c.QueryParser(filter); err != nil {
		return c.Status(400).JSON(err.Error())
	}

	type MetaInfo struct {
		TotalKendaraan int64 `json:"total_kendaraan"`
		TotalIncome    int64 `json:"total_income"`
		TotalTrx       int64 `json:"total_trx"`
	}

	var info = new(MetaInfo)

	order := models.OrderParkir{}

	start := filter.StartDate + " 00:00:00"
	end := filter.EndDate + " 23:59:59"

	database.DB.Table("order_parkirs").Select("sum(quantity) as total_kendaraan, sum(price_total) as total_income, count(*) as total_trx").Where("created_at between ? and ?", start, end).Where(order).Scan(&info)

	fmt.Println("TOTAL INCOME")
	fmt.Print(info.TotalIncome)

	// data group by ticket name

	type Result struct {
		TipeKendaraan string `json:"tipe_kendaraan"`
		Price         int64  `json:"price"`
		Total         int64  `json:"total"`
		TotalIncome   int64  `json:"total_income"`
	}

	var results []Result

	database.DB.Table("order_parkirs").Select("tarif_parkirs.name as tipe_kendaraan, tarif_parkirs.price as price, sum(quantity) as total, sum(price_total) as total_income").Joins("left join tarif_parkirs on tarif_parkirs.id = tarif_id").Where("order_parkirs.created_at between ? and ?", start, end).Where(order).Group("tarif_parkirs.name").Group("tarif_parkirs.price").Scan(&results)
	fmt.Print(results)

	// create csv file

	file, err := os.Create("./print/order_parkir.csv")
	defer file.Close()
	if err != nil {
		log.Fatalln("failed to open file", err)
	}
	w := csv.NewWriter(file)
	defer w.Flush()

	// define column headers
	headers := []string{
		" Jenis Kendaraan",
		" Price",
		" Total Masuk",
		" Total Penghasilan",
	}

	w.Write(headers)

	fmt.Println(headers)

	var data_csv [][]string
	for _, record := range results {
		row := []string{record.TipeKendaraan, strconv.Itoa(int(record.Price)), strconv.Itoa(int(record.Total)), strconv.Itoa(int(record.TotalIncome))}
		data_csv = append(data_csv, row)
	}

	w.WriteAll(data_csv)

	// // load csv

	if _, err := os.Stat(path_order_parkir()); errors.Is(err, os.ErrNotExist) {
		return c.SendStatus(404)
	}

	data_pdf := loadCSV(path_order_parkir())

	pdf := gofpdf.New("P", "mm", "A4", "")

	pdf.AddPage()

	pdf.SetFont("Helvetica", "", 12)
	pdf.WriteAligned(0, 38, fmt.Sprintf("Dari Tanggal : %s", filter.StartDate), "R")
	pdf.Ln(5)
	pdf.WriteAligned(0, 38, fmt.Sprintf("Sampai Tanggal : %s", filter.EndDate), "R")
	pdf.SetFont("Arial", "B", 24)
	pdf.WriteAligned(0, 35, "Zatobay Report", "C")

	pdf = image(pdf)
	ac := accounting.Accounting{Symbol: "Rp.", Precision: 2, Thousand: ".", Decimal: ","}

	pdf.Ln(10)
	pdf.SetFont("Helvetica", "", 12)

	pdf.WriteAligned(0, 38, fmt.Sprintf("Total Transaksi	: %d", info.TotalTrx), "L")
	pdf.Ln(5)
	pdf.WriteAligned(0, 38, fmt.Sprintf("Total Kendaraan	: %d", info.TotalKendaraan), "L")
	pdf.Ln(5)
	pdf.WriteAligned(0, 38, fmt.Sprintf("Total Pendapatan : %s", ac.FormatMoney(info.TotalIncome)), "L")
	pdf.Ln(30)

	pdf = header(pdf, data_pdf[0])
	pdf = table(pdf, data_pdf[1:])

	if pdf.Err() {
		log.Fatalf("Failed creating PDF report: %s\n", pdf.Error())
	}

	pdf_path := "./print/order_parkir.pdf"

	err_pdf := pdf.OutputFileAndClose(pdf_path)
	if err_pdf != nil {
		log.Println("ERROR", err.Error())
	}

	if _, err := os.Stat(pdf_path); errors.Is(err, os.ErrNotExist) {
		return c.SendStatus(404)
	}

	return c.Download(pdf_path)

}

func path_order_parkir() string {
	if len(os.Args) < 2 {
		return "./print/order_parkir.csv"
	}
	return os.Args[1]
}
