package businessscope

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"time"
)

const SourceSystem = "oneos"

const (
	ReasonVehicleMissing          = "VEHICLE_MISSING"
	ReasonVINMissing              = "VIN_MISSING"
	ReasonCustomerMissing         = "CUSTOMER_MISSING"
	ReasonCustomerProfileMissing  = "CUSTOMER_PROFILE_MISSING"
	ReasonContractMissing         = "CONTRACT_MISSING"
	ReasonContractProfileMissing  = "CONTRACT_PROFILE_MISSING"
	ReasonContractCustomerMissing = "CONTRACT_CUSTOMER_MISSING"
	ReasonCustomerMismatch        = "CUSTOMER_MISMATCH"
	ReasonDuplicateVehicle        = "DUPLICATE_VEHICLE_SCOPE"
	ReasonDuplicateVIN            = "DUPLICATE_VIN_SCOPE"
)

type Candidate struct {
	RowNumber             int
	VehicleID             int64
	VehiclePresent        bool
	VIN                   string
	PlateNumber           string
	CustomerID            int64
	CustomerPresent       bool
	CustomerProfileExists bool
	ContractID            int64
	ContractPresent       bool
	ContractProfileExists bool
	EffectiveCustomerID   int64
	EffectiveCustomerSet  bool
	ContractCode          string
	ProjectName           string
	OperationStatus       string
	ScopeStartAt          time.Time
	SourceUpdatedAt       *time.Time
}

type ScopeItem struct {
	VehicleID       int64
	VIN             string
	PlateNumber     string
	CustomerID      int64
	ContractID      int64
	ContractCode    string
	ProjectName     string
	OperationStatus string
	ScopeStartAt    time.Time
	SourceUpdatedAt *time.Time
}

type Rejection struct {
	RowNumber  int
	VehicleID  *int64
	VIN        string
	CustomerID *int64
	ContractID *int64
	ReasonCode string
}

type Snapshot struct {
	RunID         string
	SourceVersion string
	Checksum      string
	GeneratedAt   time.Time
	Candidates    int
	Items         []ScopeItem
	Rejections    []Rejection
}

func BuildSnapshot(candidates []Candidate, generatedAt time.Time) (Snapshot, error) {
	generatedAt = generatedAt.UTC()
	reasons := make([]string, len(candidates))
	vehicleRows := map[int64][]int{}
	vinRows := map[string][]int{}
	for i, candidate := range candidates {
		candidate.VIN = normalizeVIN(candidate.VIN)
		candidates[i] = candidate
		reasons[i] = validateCandidate(candidate)
		if reasons[i] == "" {
			vehicleRows[candidate.VehicleID] = append(vehicleRows[candidate.VehicleID], i)
			vinRows[candidate.VIN] = append(vinRows[candidate.VIN], i)
		}
	}
	for _, indexes := range vehicleRows {
		if len(indexes) > 1 {
			for _, index := range indexes {
				reasons[index] = ReasonDuplicateVehicle
			}
		}
	}
	for _, indexes := range vinRows {
		if len(indexes) > 1 {
			for _, index := range indexes {
				if reasons[index] == "" {
					reasons[index] = ReasonDuplicateVIN
				}
			}
		}
	}

	items := make([]ScopeItem, 0, len(candidates))
	rejections := make([]Rejection, 0)
	for index, candidate := range candidates {
		if reasons[index] != "" {
			rejections = append(rejections, rejectionFor(candidate, reasons[index]))
			continue
		}
		items = append(items, ScopeItem{
			VehicleID: candidate.VehicleID, VIN: candidate.VIN, PlateNumber: strings.TrimSpace(candidate.PlateNumber),
			CustomerID: candidate.CustomerID, ContractID: candidate.ContractID,
			ContractCode: strings.TrimSpace(candidate.ContractCode), ProjectName: strings.TrimSpace(candidate.ProjectName),
			OperationStatus: strings.TrimSpace(candidate.OperationStatus), ScopeStartAt: candidate.ScopeStartAt,
			SourceUpdatedAt: candidate.SourceUpdatedAt,
		})
	}
	sort.Slice(items, func(i, j int) bool {
		if items[i].CustomerID != items[j].CustomerID {
			return items[i].CustomerID < items[j].CustomerID
		}
		return items[i].VIN < items[j].VIN
	})
	sort.Slice(rejections, func(i, j int) bool { return rejections[i].RowNumber < rejections[j].RowNumber })
	canonical, err := json.Marshal(items)
	if err != nil {
		return Snapshot{}, fmt.Errorf("marshal canonical scope: %w", err)
	}
	sum := sha256.Sum256(canonical)
	checksum := hex.EncodeToString(sum[:])
	runID, err := randomHex(16)
	if err != nil {
		return Snapshot{}, err
	}
	return Snapshot{
		RunID: runID, SourceVersion: "oneos-v1:" + checksum, Checksum: checksum,
		GeneratedAt: generatedAt, Candidates: len(candidates), Items: items, Rejections: rejections,
	}, nil
}

func validateCandidate(candidate Candidate) string {
	if !candidate.VehiclePresent || candidate.VehicleID <= 0 {
		return ReasonVehicleMissing
	}
	if normalizeVIN(candidate.VIN) == "" {
		return ReasonVINMissing
	}
	if !candidate.CustomerPresent || candidate.CustomerID <= 0 {
		return ReasonCustomerMissing
	}
	if !candidate.CustomerProfileExists {
		return ReasonCustomerProfileMissing
	}
	if !candidate.ContractPresent || candidate.ContractID <= 0 {
		return ReasonContractMissing
	}
	if !candidate.ContractProfileExists {
		return ReasonContractProfileMissing
	}
	if !candidate.EffectiveCustomerSet || candidate.EffectiveCustomerID <= 0 {
		return ReasonContractCustomerMissing
	}
	if candidate.CustomerID != candidate.EffectiveCustomerID {
		return ReasonCustomerMismatch
	}
	if candidate.ScopeStartAt.IsZero() {
		return ReasonVehicleMissing
	}
	return ""
}

func rejectionFor(candidate Candidate, reason string) Rejection {
	rejection := Rejection{RowNumber: candidate.RowNumber, VIN: normalizeVIN(candidate.VIN), ReasonCode: reason}
	if candidate.VehicleID > 0 {
		rejection.VehicleID = int64Pointer(candidate.VehicleID)
	}
	if candidate.CustomerPresent && candidate.CustomerID > 0 {
		rejection.CustomerID = int64Pointer(candidate.CustomerID)
	}
	if candidate.ContractPresent && candidate.ContractID > 0 {
		rejection.ContractID = int64Pointer(candidate.ContractID)
	}
	return rejection
}

func normalizeVIN(value string) string { return strings.ToUpper(strings.TrimSpace(value)) }
func int64Pointer(value int64) *int64  { return &value }

func randomHex(bytes int) (string, error) {
	buffer := make([]byte, bytes)
	if _, err := rand.Read(buffer); err != nil {
		return "", fmt.Errorf("generate run id: %w", err)
	}
	return hex.EncodeToString(buffer), nil
}
