package store

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"

	ext "git.warky.dev/wdevs/amcs/internal/types"
)

func (db *DB) AddFamilyMember(ctx context.Context, m ext.FamilyMember) (ext.FamilyMember, error) {
	row := db.pool.QueryRow(ctx, `
		insert into family_members (name, relationship, birth_date, notes)
		values ($1, $2, $3, $4)
		returning id, created_at
	`, m.Name, nullStr(m.Relationship), m.BirthDate, nullStr(m.Notes))

	created := m
	if err := row.Scan(&created.ID, &created.CreatedAt); err != nil {
		return ext.FamilyMember{}, fmt.Errorf("insert family member: %w", err)
	}
	return created, nil
}

func (db *DB) ListFamilyMembers(ctx context.Context) ([]ext.FamilyMember, error) {
	rows, err := db.pool.Query(ctx, `select id, name, relationship, birth_date, notes, created_at from family_members order by name`)
	if err != nil {
		return nil, fmt.Errorf("list family members: %w", err)
	}
	defer rows.Close()

	var members []ext.FamilyMember
	for rows.Next() {
		var m ext.FamilyMember
		var relationship, notes *string
		if err := rows.Scan(&m.ID, &m.Name, &relationship, &m.BirthDate, &notes, &m.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan family member: %w", err)
		}
		m.Relationship = strVal(relationship)
		m.Notes = strVal(notes)
		members = append(members, m)
	}
	return members, rows.Err()
}

func (db *DB) AddActivity(ctx context.Context, a ext.Activity) (ext.Activity, error) {
	row := db.pool.QueryRow(ctx, `
		insert into activities (family_member_id, title, activity_type, day_of_week, start_time, end_time, start_date, end_date, location, notes)
		values ($1, $2, $3, $4, $5::time, $6::time, $7, $8, $9, $10)
		returning id, created_at
	`, a.FamilyMemberID, a.Title, nullStr(a.ActivityType), nullStr(a.DayOfWeek),
		nullStr(a.StartTime), nullStr(a.EndTime), a.StartDate, a.EndDate,
		nullStr(a.Location), nullStr(a.Notes))

	created := a
	if err := row.Scan(&created.ID, &created.CreatedAt); err != nil {
		return ext.Activity{}, fmt.Errorf("insert activity: %w", err)
	}
	return created, nil
}

func (db *DB) GetWeekSchedule(ctx context.Context, weekStart time.Time) ([]ext.Activity, error) {
	weekEnd := weekStart.AddDate(0, 0, 7)

	rows, err := db.pool.Query(ctx, `
		select a.id, a.family_member_id, fm.name, a.title, a.activity_type,
		       a.day_of_week, a.start_time::text, a.end_time::text,
		       a.start_date, a.end_date, a.location, a.notes, a.created_at
		from activities a
		left join family_members fm on fm.id = a.family_member_id
		where (a.start_date >= $1 and a.start_date < $2)
		   or (a.day_of_week is not null and (a.end_date is null or a.end_date >= $1))
		order by a.start_date, a.start_time
	`, weekStart, weekEnd)
	if err != nil {
		return nil, fmt.Errorf("get week schedule: %w", err)
	}
	defer rows.Close()

	return scanActivities(rows)
}

func (db *DB) SearchActivities(ctx context.Context, query, activityType string, memberID *uuid.UUID) ([]ext.Activity, error) {
	args := []any{}
	conditions := []string{}

	if q := strings.TrimSpace(query); q != "" {
		args = append(args, "%"+q+"%")
		conditions = append(conditions, fmt.Sprintf("(a.title ILIKE $%d OR a.notes ILIKE $%d)", len(args), len(args)))
	}
	if t := strings.TrimSpace(activityType); t != "" {
		args = append(args, t)
		conditions = append(conditions, fmt.Sprintf("a.activity_type = $%d", len(args)))
	}
	if memberID != nil {
		args = append(args, *memberID)
		conditions = append(conditions, fmt.Sprintf("a.family_member_id = $%d", len(args)))
	}

	q := `
		select a.id, a.family_member_id, fm.name, a.title, a.activity_type,
		       a.day_of_week, a.start_time::text, a.end_time::text,
		       a.start_date, a.end_date, a.location, a.notes, a.created_at
		from activities a
		left join family_members fm on fm.id = a.family_member_id
	`
	if len(conditions) > 0 {
		q += " where " + strings.Join(conditions, " and ")
	}
	q += " order by a.start_date, a.start_time"

	rows, err := db.pool.Query(ctx, q, args...)
	if err != nil {
		return nil, fmt.Errorf("search activities: %w", err)
	}
	defer rows.Close()

	return scanActivities(rows)
}

func (db *DB) AddImportantDate(ctx context.Context, d ext.ImportantDate) (ext.ImportantDate, error) {
	row := db.pool.QueryRow(ctx, `
		insert into important_dates (family_member_id, title, date_value, recurring_yearly, reminder_days_before, notes)
		values ($1, $2, $3, $4, $5, $6)
		returning id, created_at
	`, d.FamilyMemberID, d.Title, d.DateValue, d.RecurringYearly, d.ReminderDaysBefore, nullStr(d.Notes))

	created := d
	if err := row.Scan(&created.ID, &created.CreatedAt); err != nil {
		return ext.ImportantDate{}, fmt.Errorf("insert important date: %w", err)
	}
	return created, nil
}

func (db *DB) GetUpcomingDates(ctx context.Context, daysAhead int) ([]ext.ImportantDate, error) {
	if daysAhead <= 0 {
		daysAhead = 30
	}
	now := time.Now()
	cutoff := now.AddDate(0, 0, daysAhead)

	// For yearly recurring events, check if this year's occurrence falls in range
	rows, err := db.pool.Query(ctx, `
		select d.id, d.family_member_id, fm.name, d.title, d.date_value,
		       d.recurring_yearly, d.reminder_days_before, d.notes, d.created_at
		from important_dates d
		left join family_members fm on fm.id = d.family_member_id
		where (
			(d.recurring_yearly = false and d.date_value between $1 and $2)
			or
			(d.recurring_yearly = true and
			 make_date(extract(year from now())::int, extract(month from d.date_value)::int, extract(day from d.date_value)::int)
			 between $1 and $2)
		)
		order by d.date_value
	`, now, cutoff)
	if err != nil {
		return nil, fmt.Errorf("get upcoming dates: %w", err)
	}
	defer rows.Close()

	var dates []ext.ImportantDate
	for rows.Next() {
		var d ext.ImportantDate
		var memberID *uuid.UUID
		var memberName, notes *string
		if err := rows.Scan(&d.ID, &memberID, &memberName, &d.Title, &d.DateValue,
			&d.RecurringYearly, &d.ReminderDaysBefore, &notes, &d.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan important date: %w", err)
		}
		d.FamilyMemberID = memberID
		d.MemberName = strVal(memberName)
		d.Notes = strVal(notes)
		dates = append(dates, d)
	}
	return dates, rows.Err()
}

func scanActivities(rows interface {
	Next() bool
	Scan(...any) error
	Err() error
	Close()
}) ([]ext.Activity, error) {
	defer rows.Close()
	var activities []ext.Activity
	for rows.Next() {
		var a ext.Activity
		var memberName, activityType, dayOfWeek, startTime, endTime, location, notes *string
		if err := rows.Scan(
			&a.ID, &a.FamilyMemberID, &memberName, &a.Title, &activityType,
			&dayOfWeek, &startTime, &endTime,
			&a.StartDate, &a.EndDate, &location, &notes, &a.CreatedAt,
		); err != nil {
			return nil, fmt.Errorf("scan activity: %w", err)
		}
		a.MemberName = strVal(memberName)
		a.ActivityType = strVal(activityType)
		a.DayOfWeek = strVal(dayOfWeek)
		a.StartTime = strVal(startTime)
		a.EndTime = strVal(endTime)
		a.Location = strVal(location)
		a.Notes = strVal(notes)
		activities = append(activities, a)
	}
	return activities, rows.Err()
}
