package darwindb

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	pgx "github.com/jackc/pgx/v4"
	"hg.lukegb.com/lukegb/depot/go/trains/darwin"
)

type scheduleMode int

const (
	scheduleModeOverwrite scheduleMode = iota
	scheduleModeIgnoreExisting
)

// handleSchedule processes a Darwin Schedule.
func handleSchedule(ctx context.Context, tx pgx.Tx, s *darwin.Schedule, a *Affected, sm scheduleMode) error {
	startTS, err := startTime(s)
	if err != nil {
		return fmt.Errorf("finding start time for RID %v: %w", s.RID, err)
	}

	var cancelCode, cancelTIPLOC *string
	var cancelTIPLOCNear *bool
	if s.CancellationReason != nil {
		cancelCode = &s.CancellationReason.Code
		cancelTIPLOC = &s.CancellationReason.TIPLOC
		cancelTIPLOCNear = &s.CancellationReason.Near
	}
	sql := `
INSERT INTO train_services
(rid, uid, rsid, headcode, scheduled_start_date, effective_start_time,
 train_operator, status, train_category, is_passenger_svc, is_charter, is_q_train,
 cancellation_reason_code, cancellation_reason_tiploc, cancellation_reason_tiploc_near,
 active, deleted, cancelled)
VALUES
($1, $2, $3, $4, $5, $6,
 $7, COALESCE($8, 'P'), COALESCE($9, 'OO'), COALESCE($10, true), COALESCE($11, false), COALESCE($12, false),
 $13, $14, $15,
 COALESCE($16, true), $17, COALESCE($18, false))
ON CONFLICT (rid)`
	switch sm {
	case scheduleModeOverwrite:
		sql += ` DO UPDATE SET
uid=$2, rsid=$3, headcode=$4, scheduled_start_date=$5, effective_start_time=$6,
train_operator=$7, status=COALESCE($8, 'P'), train_category=COALESCE($9, 'OO'), is_passenger_svc=COALESCE($10, true), is_charter=COALESCE($11, false), is_q_train=COALESCE($12, train_services.is_q_train),
cancellation_reason_code=$13, cancellation_reason_tiploc=$14, cancellation_reason_tiploc_near=$15,
active=COALESCE($16, 'true'), deleted=$17, cancelled=COALESCE($18, 'false')
`
	case scheduleModeIgnoreExisting:
		sql += ` DO NOTHING
`
	}
	sql += `RETURNING id`
	row := tx.QueryRow(ctx, sql,
		s.RID, s.UID, s.RSID, s.TrainID, s.SSD, startTS,
		s.TOC, s.Status, s.TrainCat, s.IsPassengerSvc, s.IsCharter, s.IsQTrain,
		cancelCode, cancelTIPLOC, cancelTIPLOCNear,
		s.IsActive, s.Deleted, s.Cancelled)
	var serviceID int
	if err := row.Scan(&serviceID); err == pgx.ErrNoRows && sm == scheduleModeIgnoreExisting {
		// The row existed already.
		return nil
	} else if err != nil {
		log.Printf("inserting schedule for RID %v: %v", s.RID, err)
		return fmt.Errorf("inserting schedule for RID %v: %w", s.RID, err)
	}
	a.RID(s.RID)
	a.TSID(serviceID)

	// Insert/update all the locations.
	var activeLocations []int
	for _, cp := range s.CallingPoints {
		var locid int
		row := tx.QueryRow(ctx, `
SELECT train_locations_upsert_from_schedule(
p_tsid => $1, p_rid => $2, p_tiploc => $3, p_calling_point => $4,
p_schedule_public_arrival => $5, p_schedule_working_arrival => $6,
p_schedule_public_departure => $7, p_schedule_working_departure => $8,
p_schedule_working_pass => $9,
p_schedule_route_delay => $10, p_schedule_activity => $11, p_schedule_planned_activity => $12, p_schedule_cancelled => $13,
p_schedule_false_destination_tiploc => $14, p_schedule_platform => $15)
`,
			serviceID, s.RID, cp.TIPLOC, cp.XMLName.Local,
			timeToTimestamp(cp.PTA, startTS), timeToTimestamp(cp.WTA, startTS),
			timeToTimestamp(cp.PTD, startTS), timeToTimestamp(cp.WTD, startTS),
			timeToTimestamp(cp.WTP, startTS),
			cp.RDelay, cp.Act, cp.PlanAct, cp.Cancelled,
			cp.FD, cp.Platform,
		)
		if err := row.Scan(&locid); err != nil {
			log.Printf("updating location %v from RID %v/my ID %v: %v", cp.TIPLOC, s.RID, serviceID, err)
			zz, _ := json.MarshalIndent(s, "", "  ")
			log.Println(string(zz))
			zz, _ = json.MarshalIndent(cp, "", "  ")
			log.Println(string(zz))
			return fmt.Errorf("updating location %v from RID %v/my ID %v: %w", cp.TIPLOC, s.RID, serviceID, err)
		} else {
			activeLocations = append(activeLocations, locid)
		}
		a.TIPLOC(cp.TIPLOC)
	}
	if _, err := tx.Exec(ctx, "UPDATE train_locations SET active_in_schedule=false WHERE tsid=$1 AND NOT (id = ANY ($2))", serviceID, activeLocations); err != nil {
		return fmt.Errorf("marking old locations as removed from schedule for RID %v/my ID %v (active locations: %v): %w", s.RID, serviceID, activeLocations, err)
	}
	fmt.Printf("s")
	return nil
}