package handlers

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	"otaupdate/services"

	"github.com/fiskerinc/cloud-services/pkg/clickhouse"
	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/utils"
	"github.com/fiskerinc/cloud-services/pkg/validator"

	ch "github.com/ClickHouse/clickhouse-go/v2"
	"github.com/gorilla/schema"
	"github.com/pkg/errors"
	"github.com/fiskerinc/cloud-services/pkg/loggerdataresp"
)

// HandleECUStatsGetList godoc
// @Summary List API tokens
// @Description List API tokens. Requires API token permission
// @Accept  json
// @Produce  json
// @Param Authorization header string false "Bearer <ID token>"
// @Param Api-Key header string false "<API token>"
// @Param ecus query []string true "ECU names"
// @Param dbcs query []string true "DBC hashes"
// @Param vins query []string true "Array of VINs"
// @Param hours query int true "Past hours  that must be included into the request"
// @Param min_zero_pct query float32 true "Minimum zero values percent"
// @Param min_out_of_range_pct query int true "Minimum out of range percent"
// @Success 200 {object} common.JSONDBQueryResult{data=[]common.ECUStat}
// @Failure 400 {object} common.JSONError "Bad request"
// @Failure 401 {object} common.JSONError "Unauthorized"
// @Failure 503 {object} common.JSONError "Service unavailable"
// @Router /ecu_stats [get]
func HandleECUStatsGetList(w http.ResponseWriter, r *http.Request) {
	conn, err := services.GetClickhouseConn()
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
		logger.Error().Err(err).Msg("cannot get clickhouse client")

		return
	}

	filter, err := parseStatsFilter(r)
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusBadRequest) {
		return
	}

	stats, err := getEcusStats(conn, filter)
	if loggerdataresp.BadDataErrorResp(w, err, http.StatusServiceUnavailable) {
		return
	}

	utils.RespJSON(w, http.StatusOK, common.JSONDBQueryResult{Data: stats})
}

func parseStatsFilter(r *http.Request) (common.StatsFilter, error) {
	sch := schema.NewDecoder()
	filter := common.StatsFilter{}

	sch.SetAliasTag("json")
	err := sch.Decode(&filter, r.URL.Query())
	if err != nil {
		return common.StatsFilter{}, errors.WithStack(err)
	}

	err = validator.GetValidator().Struct(filter)
	if err != nil {
		return common.StatsFilter{}, errors.WithStack(err)
	}

	return filter, nil
}

func getEcusStats(conn clickhouse.ConnInterface, filter common.StatsFilter) ([]common.ECUStat, error) {
	var result []common.ECUStat

	chCtx := ch.Context(context.Background(), ch.WithParameters(ch.Parameters{
		"minOutOfRangePct": fmt.Sprint(filter.MinOutOfRangePct),
		"minZeroPct":       fmt.Sprint(filter.MinZeroPct),
		"hours":            fmt.Sprint(filter.Hours),
		"vins":             "['" + strings.Join(filter.VINs, "','") + "']",
		"dbcs":             "['" + strings.Join(filter.DBCs, "','") + "']",
		"ecus":             "['" + strings.Join(filter.ECUs, "','") + "']",
	}))

	if err := conn.Select(chCtx, &result, `select ecu_name,
       sum(case when value_out_range_pct> {minOutOfRangePct:Float32} then 1 else 0 end) as signals_w_incorrect_values,
       sum(case when zero_pct> {minZeroPct:Float32} then 1 else 0 end ) as signals_all_zero,
       count(*) as number_of_ecu_signals,
       sum(tot_cnt) as total_signal_records,
       (signals_w_incorrect_values/number_of_ecu_signals) incorrect_val_signal_pct,
       signals_all_zero/number_of_ecu_signals as zero_signals_pct
from ( select
           case when aa.Name<>'' then aa.Name
                when bb.signal_name<>'' then bb.signal_name
                else null end as signal_name,
           case when aa.ID<>'0' then aa.ID
                when bb.message_id<>'0' then bb.message_id
                else null end as can_id,
           case when aa.ecu_name<>'' then aa.ecu_name
                when dbcm.ecu_name<>'' then dbcm.ecu_name
                else null end as ecu_name,
           zero_count,value_out_range_cnt, tot_cnt,
           case when tot_cnt <> 0 then value_out_range_pct else 0 end as value_out_range_pct,
           case when tot_cnt <> 0 then zero_pct else 0 end as zero_pct

       from

           (/* check if signals are within dbc value range and if 0 on joined CAN signals and dbc*/
               select Name, ID, signal_name, ecu_name,cycle_time_ns,sender_node,
                      sum(case when Value=0 then 1 else 0 end) as zero_count,
                      sum(case when a.Value<b.min  or a.Value>b.max then 1 else 0 end) as value_out_range_cnt,
                      count(*) as tot_cnt,
                      value_out_range_cnt/tot_cnt as value_out_range_pct,
                      zero_count/tot_cnt as zero_pct
               from vehicle_signal  as a
                        inner join

                    (/* select dbc_messages and dbc_signals */
                        select b1.*, b2.message_id, b2.ecu_name, b2.cycle_time_ns, b2.sender_node
                        from dbc_signals as b1
                                 inner join dbc_messages as b2
                                            on b1.dbc_hash=b2.dbc_hash
                                                and b1.message_id=b2.message_id
                        where b1.dbc_hash in {dbcs:Array(String)}

                    )  as b
                    on a.Name=b.signal_name

               where
                       a.Timestamp> (select max(Timestamp) from vehicle_signal where VIN in {vins:Array(String)}) - toIntervalHour({hours:UInt64})
        and 
        a.VIN in {vins:Array(String)}
       group by 1,2,3,4,5,6
     ) as aa
         full outer join dbc_signals as bb
                         on aa.Name=bb.signal_name
                             and aa.ID=bb.message_id
         inner join dbc_messages as dbcm
                    on bb.message_id=dbcm.message_id
where bb.dbc_hash in {dbcs:Array(String)} and dbcm.dbc_hash in {dbcs:Array(String)}
    and ecu_name in {ecus:Array(String)})
group by ecu_name
order by zero_signals_pct desc, incorrect_val_signal_pct desc`); err != nil {
		return result, err
	}

	return result, nil
}
