package finance

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/shopspring/decimal"
)

type fakePeriodStore struct {
	Store
	periods  map[string]AccountingPeriod
	summary  PeriodSummary
	logs     []AccountingPeriodLog
	closedBy int64
}

func newFakePeriodStore() *fakePeriodStore {
	return &fakePeriodStore{
		periods: make(map[string]AccountingPeriod),
		summary: PeriodSummary{
			CashflowIn:       decimal.NewFromInt(15000000),
			CashflowOut:      decimal.NewFromInt(8000000),
			NetCashflow:      decimal.NewFromInt(7000000),
			CashflowCount:    25,
			SalesCount:       18,
			SalesTotal:       decimal.NewFromInt(14500000),
			JournalCount:     12,
			JournalDebit:     decimal.NewFromInt(15000000),
			JournalCredit:    decimal.NewFromInt(15000000),
			UnpostedJournals: 0,
			IsBalanced:       true,
		},
	}
}

func (f *fakePeriodStore) key(y, m int16) string {
	return time.Date(int(y), time.Month(m), 1, 0, 0, 0, 0, time.UTC).Format("2006-01")
}

func (f *fakePeriodStore) ListAccountingPeriods(ctx context.Context) ([]AccountingPeriod, error) {
	out := make([]AccountingPeriod, 0, len(f.periods))
	for _, p := range f.periods {
		out = append(out, p)
	}
	return out, nil
}

func (f *fakePeriodStore) AccountingPeriod(ctx context.Context, y, m int16) (AccountingPeriod, error) {
	p, ok := f.periods[f.key(y, m)]
	if !ok {
		return AccountingPeriod{
			PeriodYear:  y,
			PeriodMonth: m,
			Status:      "open",
			StartDate:   time.Date(int(y), time.Month(m), 1, 0, 0, 0, 0, time.UTC).Format("2006-01-02"),
			EndDate:     time.Date(int(y), time.Month(m)+1, 0, 0, 0, 0, 0, time.UTC).Format("2006-01-02"),
		}, nil
	}
	return p, nil
}

func (f *fakePeriodStore) AccountingPeriodSummary(ctx context.Context, y, m int16) (PeriodSummary, error) {
	return f.summary, nil
}

func (f *fakePeriodStore) ValidateAccountingPeriod(ctx context.Context, y, m int16) (PeriodValidationResult, error) {
	var issues []string
	p, _ := f.AccountingPeriod(ctx, y, m)
	if p.Status == "locked" {
		issues = append(issues, "Periode ini sudah dalam status terkunci")
	}
	if f.summary.UnpostedJournals > 0 {
		issues = append(issues, "Terdapat jurnal draft yang belum diposting")
	}
	if !f.summary.IsBalanced {
		issues = append(issues, "Jurnal tidak seimbang")
	}
	return PeriodValidationResult{
		CanClose: len(issues) == 0 && p.Status != "locked",
		Issues:   issues,
		Summary:  f.summary,
	}, nil
}

func (f *fakePeriodStore) CloseAccountingPeriod(ctx context.Context, y, m int16, actor int64) (AccountingPeriod, error) {
	k := f.key(y, m)
	now := time.Now()
	p := AccountingPeriod{
		ID:          1,
		PeriodYear:  y,
		PeriodMonth: m,
		Status:      "locked",
		LockedAt:    &now,
		LockedBy:    &actor,
		Snapshot: map[string]any{
			"closed_at":      now.Format(time.RFC3339),
			"cashflow_in":    f.summary.CashflowIn.String(),
			"cashflow_out":   f.summary.CashflowOut.String(),
			"net_cashflow":   f.summary.NetCashflow.String(),
			"cashflow_count": f.summary.CashflowCount,
			"is_balanced":    f.summary.IsBalanced,
		},
	}
	f.periods[k] = p
	f.closedBy = actor
	f.logs = append(f.logs, AccountingPeriodLog{
		ID:          int64(len(f.logs) + 1),
		PeriodYear:  y,
		PeriodMonth: m,
		Action:      "close",
		ActorID:     actor,
		CreatedAt:   now,
	})
	return p, nil
}

func (f *fakePeriodStore) ReopenAccountingPeriod(ctx context.Context, y, m int16, actor int64, reason string) (AccountingPeriod, error) {
	k := f.key(y, m)
	p, ok := f.periods[k]
	if !ok || p.Status != "locked" {
		return p, errors.New("period is not locked")
	}
	now := time.Now()
	p.Status = "reopened"
	p.ReopenedAt = &now
	p.ReopenedBy = &actor
	p.ReopenReason = &reason
	f.periods[k] = p
	f.logs = append(f.logs, AccountingPeriodLog{
		ID:          int64(len(f.logs) + 1),
		PeriodYear:  y,
		PeriodMonth: m,
		Action:      "reopen",
		ActorID:     actor,
		Reason:      &reason,
		CreatedAt:   now,
	})
	return p, nil
}

func (f *fakePeriodStore) AccountingPeriodHistory(ctx context.Context, y, m int16) ([]AccountingPeriodLog, error) {
	return f.logs, nil
}

func TestPeriodClosing_Lifecycle(t *testing.T) {
	store := newFakePeriodStore()
	svc := NewService(nil, nil, time.UTC)
	svc.store = store

	ctx := context.Background()

	// 1. Preview
	summary, err := svc.PreviewAccountingPeriod(ctx, 2026, 10)
	if err != nil {
		t.Fatalf("Preview failed: %v", err)
	}
	if !summary.IsBalanced {
		t.Errorf("Expected balanced summary, got false")
	}
	if summary.CashflowCount != 25 {
		t.Errorf("Expected 25 cashflows, got %d", summary.CashflowCount)
	}

	// 2. Validate
	valRes, err := svc.ValidateAccountingPeriod(ctx, 2026, 10)
	if err != nil {
		t.Fatalf("Validate failed: %v", err)
	}
	if !valRes.CanClose {
		t.Errorf("Expected CanClose=true, got false, issues: %v", valRes.Issues)
	}

	// 3. Close
	closed, err := svc.CloseAccountingPeriod(ctx, 2026, 10, 42)
	if err != nil {
		t.Fatalf("Close failed: %v", err)
	}
	if closed.Status != "locked" {
		t.Errorf("Expected status 'locked', got %s", closed.Status)
	}
	if closed.Snapshot == nil {
		t.Errorf("Expected snapshot to be saved, got nil")
	}

	// 4. Validate when locked
	valLocked, err := svc.ValidateAccountingPeriod(ctx, 2026, 10)
	if err != nil {
		t.Fatalf("Validate locked failed: %v", err)
	}
	if valLocked.CanClose {
		t.Errorf("Expected locked period CanClose=false, got true")
	}

	// 5. Reopen without reason should fail
	_, err = svc.ReopenAccountingPeriod(ctx, 2026, 10, 42, "")
	if err == nil {
		t.Fatalf("Expected error reopening without reason, got nil")
	}

	// 6. Reopen with valid reason
	reopened, err := svc.ReopenAccountingPeriod(ctx, 2026, 10, 42, "Audit koreksi transaksi akhir bulan")
	if err != nil {
		t.Fatalf("Reopen failed: %v", err)
	}
	if reopened.Status != "reopened" {
		t.Errorf("Expected status 'reopened', got %s", reopened.Status)
	}
	if reopened.ReopenReason == nil || *reopened.ReopenReason != "Audit koreksi transaksi akhir bulan" {
		t.Errorf("Expected correct reopen reason, got %v", reopened.ReopenReason)
	}

	// 7. Audit history
	history, err := svc.AccountingPeriodHistory(ctx, 2026, 10)
	if err != nil {
		t.Fatalf("History failed: %v", err)
	}
	if len(history) != 2 {
		t.Fatalf("Expected 2 log entries (close and reopen), got %d", len(history))
	}
	if history[0].Action != "close" || history[1].Action != "reopen" {
		t.Errorf("Expected actions close then reopen, got %s, %s", history[0].Action, history[1].Action)
	}
}
