package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/kami/maven/internal/ipc"
)

type fakeCore struct {
	ipc.UnimplementedCoreAPI
	facts    map[string]ipc.Fact // composite key "key|source" → Fact
	writeLog []ipc.WriteFactReq
	writeErr error
	readErr  error
}

func (f *fakeCore) LatestFactBySource(_ context.Context, key, source string) (ipc.Fact, error) {
	if f.readErr != nil {
		return ipc.Fact{}, f.readErr
	}
	if f.facts == nil {
		return ipc.Fact{}, ipc.ErrNoFact
	}
	fk := key + "|" + source
	fact, ok := f.facts[fk]
	if !ok {
		return ipc.Fact{}, ipc.ErrNoFact
	}
	return fact, nil
}

func (f *fakeCore) WriteFact(_ context.Context, req ipc.WriteFactReq) (int64, error) {
	if f.writeErr != nil {
		return 0, f.writeErr
	}
	if f.facts == nil {
		f.facts = make(map[string]ipc.Fact)
	}
	fk := req.Key + "|" + req.Source
	f.facts[fk] = ipc.Fact{
		Key:    req.Key,
		Value:  req.Value,
		Source: req.Source,
	}
	f.writeLog = append(f.writeLog, req)
	return int64(len(f.writeLog)), nil
}

// ---------------------------------------------------------------------------
// Core logic tests
// ---------------------------------------------------------------------------

func TestWriteIfChanged(t *testing.T) {
	ctx := context.Background()
	now := time.Date(2026, 7, 3, 12, 0, 0, 0, time.UTC)

	t.Run("no previous fact writes", func(t *testing.T) {
		fc := &fakeCore{}
		p := &poller{core: fc}
		err := p.writeIfChanged(ctx, "test_key", "poll:caldav", "hello", now)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(fc.writeLog) != 1 {
			t.Fatalf("expected 1 write, got %d", len(fc.writeLog))
		}
		if fc.writeLog[0].Value != "hello" {
			t.Errorf("value = %q, want %q", fc.writeLog[0].Value, "hello")
		}
		if fc.writeLog[0].Key != "test_key" {
			t.Errorf("key = %q, want %q", fc.writeLog[0].Key, "test_key")
		}
		if fc.writeLog[0].Source != "poll:caldav" {
			t.Errorf("source = %q, want %q", fc.writeLog[0].Source, "poll:caldav")
		}
		if fc.writeLog[0].Kind != "env" {
			t.Errorf("kind = %q, want %q", fc.writeLog[0].Kind, "env")
		}
	})

	t.Run("same value skips write", func(t *testing.T) {
		fc := &fakeCore{
			facts: map[string]ipc.Fact{
				"test_key|poll:caldav": {Value: "hello"},
			},
		}
		p := &poller{core: fc}
		err := p.writeIfChanged(ctx, "test_key", "poll:caldav", "hello", now)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(fc.writeLog) != 0 {
			t.Errorf("expected 0 writes, got %d", len(fc.writeLog))
		}
	})

	t.Run("different value writes", func(t *testing.T) {
		fc := &fakeCore{
			facts: map[string]ipc.Fact{
				"test_key|poll:caldav": {Value: "old"},
			},
		}
		p := &poller{core: fc}
		err := p.writeIfChanged(ctx, "test_key", "poll:caldav", "new", now)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(fc.writeLog) != 1 {
			t.Fatalf("expected 1 write, got %d", len(fc.writeLog))
		}
		if fc.writeLog[0].Value != "new" {
			t.Errorf("value = %q, want %q", fc.writeLog[0].Value, "new")
		}
	})

	t.Run("read error other than ErrNoFact returns error", func(t *testing.T) {
		fc := &fakeCore{readErr: fmt.Errorf("connection refused")}
		p := &poller{core: fc}
		err := p.writeIfChanged(ctx, "fail_key", "poll:caldav", "x", now)
		if err == nil {
			t.Fatal("expected error, got nil")
		}
	})

	t.Run("write error returns error", func(t *testing.T) {
		fc := &fakeCore{
			facts:    map[string]ipc.Fact{},
			writeErr: fmt.Errorf("disk full"),
		}
		p := &poller{core: fc}
		err := p.writeIfChanged(ctx, "test_key", "poll:caldav", "hello", now)
		if err == nil {
			t.Fatal("expected error, got nil")
		}
	})
}

// ---------------------------------------------------------------------------
// Poll cycle test
// ---------------------------------------------------------------------------

func TestPollOnce(t *testing.T) {
	now := time.Now().UTC()
	start := now.Add(-2 * time.Hour).Truncate(time.Second)
	end := now.Add(2 * time.Hour).Truncate(time.Second)

	ical := fmt.Sprintf("BEGIN:VCALENDAR\nBEGIN:VEVENT\nDTSTART:%sT%sZ\nDTEND:%sT%sZ\nSUMMARY:Current meeting\nEND:VEVENT\nEND:VCALENDAR",
		start.Format("20060102"), start.Format("150405"),
		end.Format("20060102"), end.Format("150405"))

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(ical))
	}))
	defer srv.Close()

	fc := &fakeCore{}
	p := &poller{
		core: fc,
		http: srv.Client(),
		url:  srv.URL,
	}

	p.pollOnce(context.Background())

	if len(fc.writeLog) != 2 {
		t.Fatalf("expected 2 writes (calendar_busy + calendar_event), got %d", len(fc.writeLog))
	}

	// First write: calendar_busy = "true"
	busyReq := fc.writeLog[0]
	if busyReq.Key != "calendar_busy" {
		t.Errorf("first write key = %q, want %q", busyReq.Key, "calendar_busy")
	}
	if busyReq.Value != "true" {
		t.Errorf("calendar_busy value = %q, want %q", busyReq.Value, "true")
	}
	if busyReq.Source != "poll:caldav" {
		t.Errorf("source = %q, want %q", busyReq.Source, "poll:caldav")
	}
	if busyReq.Kind != "env" {
		t.Errorf("kind = %q, want %q", busyReq.Kind, "env")
	}
	if busyReq.Ts.IsZero() {
		t.Errorf("calendar_busy ts is zero")
	}

	// Second write: calendar_event_<date>_<summary> = "<summary> @ HH:MM-HH:MM".
	// The iCal states the event in UTC and the fact is stamped on the owner's
	// clock, so the expected key date and times are the local reading of it.
	eventReq := fc.writeLog[1]
	expectedKey := "calendar_event_" + start.Local().Format("20060102") + "_Current-meeting"
	if eventReq.Key != expectedKey {
		t.Errorf("event key = %q, want %q", eventReq.Key, expectedKey)
	}
	expectedVal := "Current meeting @ " + start.Local().Format("15:04") + "-" + end.Local().Format("15:04")
	if eventReq.Value != expectedVal {
		t.Errorf("event value = %q, want %q", eventReq.Value, expectedVal)
	}
	if eventReq.Source != "poll:caldav" {
		t.Errorf("event source = %q, want %q", eventReq.Source, "poll:caldav")
	}
	if eventReq.Kind != "env" {
		t.Errorf("event kind = %q, want %q", eventReq.Kind, "env")
	}
	if !eventReq.Ts.Equal(start) {
		t.Errorf("event ts = %v, want %v", eventReq.Ts, start)
	}
}
