package control

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"time"

	"google.golang.org/api/googleapi"
	loggingapi "google.golang.org/api/logging/v2"
)

// Cloud Scheduler emits independent start/end entries; finishes have no attempt
// identity in the observed contract. Never correlate by timestamp proximity.
// https://cloud.google.com/scheduler/docs/viewing-logs
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/list
var schedulerResourcePattern = regexp.MustCompile(`^projects/([A-Za-z0-9_-]+)/locations/([a-z0-9-]+)/jobs/([A-Za-z0-9_-]+)$`)
var schedulerHTTPCode = regexp.MustCompile(`Original HTTP response code number = ([0-9]{3})`)
var schedulerStatusPattern = regexp.MustCompile(`^[A-Z_]{1,40}$`)

type schedulerLogCursor struct {
	From      time.Time
	Until     time.Time
	PageToken string
}

func (s *ExternalLiveSource) schedulerLogsHistory(ctx context.Context, previous CollectionState) (RunBatch, error) {
	parts := schedulerResourcePattern.FindStringSubmatch(s.config.SchedulerJobName)
	if parts == nil {
		return RunBatch{}, fmt.Errorf("invalid Scheduler log scope")
	}
	if s.logging == nil {
		return RunBatch{}, fmt.Errorf("Scheduler log reader unavailable")
	}
	now := time.Now().UTC()
	cursor := schedulerLogCursor{From: now.AddDate(0, 0, -30), Until: now}
	if previous.Cursor != "" {
		if err := json.Unmarshal([]byte(previous.Cursor), &cursor); err != nil || cursor.PageToken == "" || !cursor.From.Before(cursor.Until) || cursor.Until.After(now.Add(time.Minute)) || cursor.From.Before(now.AddDate(0, 0, -31)) {
			return RunBatch{State: CollectionState{Status: "unavailable", Message: "调度日志游标过期或无效；保留已采集证据，下轮重扫有限窗口"}}, nil
		}
	}
	filter := fmt.Sprintf(`resource.type="cloud_scheduler_job" AND resource.labels.project_id=%q AND resource.labels.location=%q AND resource.labels.job_id=%q AND timestamp>=%q AND timestamp<=%q AND (jsonPayload."@type"="type.googleapis.com/google.cloud.scheduler.logging.AttemptStarted" OR jsonPayload."@type"="type.googleapis.com/google.cloud.scheduler.logging.AttemptFinished")`, parts[1], parts[2], parts[3], cursor.From.Format(time.RFC3339Nano), cursor.Until.Format(time.RFC3339Nano))
	response, err := s.logging.Entries.List(&loggingapi.ListLogEntriesRequest{ResourceNames: []string{"projects/" + parts[1]}, Filter: filter, OrderBy: "timestamp desc", PageSize: 200, PageToken: cursor.PageToken}).Context(ctx).Do()
	if err != nil {
		var apiErr *googleapi.Error
		if cursor.PageToken != "" && errors.As(err, &apiErr) && apiErr.Code == 400 {
			return RunBatch{State: CollectionState{Status: "unavailable", Message: "日志分页令牌被服务拒绝；保留记录，下轮重新扫描"}}, nil
		}
		return RunBatch{}, fmt.Errorf("Scheduler log read failed")
	}
	if len(response.Entries) > 200 {
		return RunBatch{}, fmt.Errorf("Scheduler log page exceeds limit")
	}
	b := RunBatch{State: CollectionState{Status: "partial", CoveredFrom: cursor.From, Message: "近30天日志窗口已扫描；受源保留/延迟限制。开始与响应分别留痕，无尝试ID时不合并、不推断业务执行"}}
	for _, entry := range response.Entries {
		trigger, err := schedulerLogTrigger(entry, s.config.SchedulerJobName, now)
		if err != nil {
			return RunBatch{}, err
		}
		if trigger.At.Before(cursor.From) || trigger.At.After(cursor.Until) {
			return RunBatch{}, fmt.Errorf("Scheduler log outside requested window")
		}
		b.Triggers = append(b.Triggers, trigger)
	}
	if response.NextPageToken != "" {
		cursor.PageToken = response.NextPageToken
		encoded, _ := json.Marshal(cursor)
		b.State.Cursor = string(encoded)
		b.State.Message = "近30天调度日志正在分页回补；开始/响应是独立证据，扫描尚未完成"
	}
	return b, nil
}

func schedulerLogTrigger(entry *loggingapi.LogEntry, job string, now time.Time) (TriggerObservation, error) {
	parts := schedulerResourcePattern.FindStringSubmatch(job)
	if entry == nil || parts == nil || entry.Resource == nil || entry.Resource.Type != "cloud_scheduler_job" || entry.InsertId == "" || entry.LogName == "" {
		return TriggerObservation{}, fmt.Errorf("missing Scheduler log identity")
	}
	labels := entry.Resource.Labels
	if labels["project_id"] != parts[1] || labels["location"] != parts[2] || labels["job_id"] != parts[3] {
		return TriggerObservation{}, fmt.Errorf("Scheduler log scope mismatch")
	}
	at, err := time.Parse(time.RFC3339Nano, entry.Timestamp)
	if err != nil || at.IsZero() {
		return TriggerObservation{}, fmt.Errorf("invalid Scheduler log timestamp")
	}
	var payload struct {
		Type      string `json:"@type"`
		Job       string `json:"jobName"`
		Scheduled string `json:"scheduledTime"`
		Status    string `json:"status"`
		Debug     string `json:"debugInfo"`
	}
	if json.Unmarshal(entry.JsonPayload, &payload) != nil || payload.Job != job {
		return TriggerObservation{}, fmt.Errorf("invalid Scheduler log contract")
	}
	t := TriggerObservation{ID: stableID(job, "scheduler_log", entry.LogName, entry.InsertId, stamp(at)), ScheduleID: job, At: at, ObservedAt: now, EvidenceKind: "scheduler_log", Status: "unknown", Message: "独立日志证据；未关联实际执行，不按时间相邻合并"}
	switch payload.Type {
	case "type.googleapis.com/google.cloud.scheduler.logging.AttemptStarted":
		t.Phase, t.Status = "started", "dispatch_started"
		if payload.Scheduled != "" {
			scheduled, err := time.Parse(time.RFC3339Nano, payload.Scheduled)
			if err != nil || scheduled.IsZero() {
				return TriggerObservation{}, fmt.Errorf("invalid scheduled time")
			}
			t.ScheduledAt = &scheduled
		}
	case "type.googleapis.com/google.cloud.scheduler.logging.AttemptFinished":
		t.Phase = "finished"
		if payload.Status != "" {
			if !schedulerStatusPattern.MatchString(payload.Status) {
				return TriggerObservation{}, fmt.Errorf("invalid Scheduler status code")
			}
			t.RawStatus = payload.Status
		}
		if match := schedulerHTTPCode.FindStringSubmatch(payload.Debug); match != nil {
			t.HTTPStatus, _ = strconv.Atoi(match[1])
		}
		if payload.Status == "OK" || (payload.Status == "" && t.HTTPStatus >= 200 && t.HTTPStatus < 300) {
			t.Status = "accepted"
		}
		switch payload.Status {
		case "CANCELLED", "UNKNOWN", "INVALID_ARGUMENT", "DEADLINE_EXCEEDED", "NOT_FOUND", "ALREADY_EXISTS", "PERMISSION_DENIED", "RESOURCE_EXHAUSTED", "FAILED_PRECONDITION", "ABORTED", "OUT_OF_RANGE", "UNIMPLEMENTED", "INTERNAL", "UNAVAILABLE", "DATA_LOSS", "UNAUTHENTICATED":
			t.Status = "failed"
		}
		if t.HTTPStatus >= 400 {
			t.Status = "failed"
		}
		// A missing/novel provider status is unknown, never default success.
	default:
		return TriggerObservation{}, fmt.Errorf("unsupported Scheduler log event")
	}
	return t, nil
}
