package dav

import (
	"context"
	"crussell/clock"
	"fmt"
	"log/slog"
	"strings"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
)

// BaseService holds shared DB connection
type BaseService struct {
	db *pgxpool.Pool
}

// newBaseService returns a new BaseService instance
func newBaseService(db *pgxpool.Pool) *BaseService {
	return &BaseService{db: db}
}

// ============================================================================
// Calendar Functions
// ============================================================================

func (s *BaseService) ListEventsForMonth(year int, month time.Month) ([]CalendarEvent, error) {
	start := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
	end := start.AddDate(0, 1, 0).Add(-time.Second)

	query := `
		SELECT id, calendarid, uri, calendardata, lastmodified, etag, size, componenttype,
		       firstoccurence, lastoccurence, uid
		FROM dav_calendarobjects
		WHERE firstoccurence >= $1 AND firstoccurence <= $2
		ORDER BY firstoccurence
	`
	return s.queryEventsWithContacts(query, start.Unix(), end.Unix())
}

func (s *BaseService) ListEventsBetween(start, end time.Time) ([]CalendarEvent, error) {
	query := `
		SELECT id, calendarid, uri, calendardata, lastmodified, etag, size, componenttype,
		       firstoccurence, lastoccurence, uid
		FROM dav_calendarobjects
		WHERE firstoccurence >= $1 AND firstoccurence <= $2
		ORDER BY firstoccurence
	`
	return s.queryEventsWithContacts(query, start.Unix(), end.Unix())
}

func (s *BaseService) ListEventsTomorrow() ([]CalendarEvent, error) {
	now := clock.Now()
	tomorrow := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC)
	dayAfter := tomorrow.Add(24 * time.Hour)
	return s.ListEventsBetween(tomorrow, dayAfter)
}

func (s *BaseService) ListEventsThisWeek() ([]CalendarEvent, error) {
	now := clock.Now()
	weekday := int(now.Weekday())
	if weekday == 0 { // Sunday
		weekday = 7
	}
	monday := now.AddDate(0, 0, -weekday+1)
	monday = time.Date(monday.Year(), monday.Month(), monday.Day(), 0, 0, 0, 0, time.UTC)
	sunday := monday.AddDate(0, 0, 7)
	return s.ListEventsBetween(monday, sunday)
}

// ============================================================================
// Contact Functions
// ============================================================================

func (s *BaseService) GetContactByURI(addressBookID int, uri string) (*Contact, error) {
	query := `
		SELECT id, addressbookid, uri, carddata, lastmodified, etag, size
		FROM dav_cards
		WHERE addressbookid = $1 AND uri = $2
	`
	var c Contact
	err := s.db.QueryRow(context.Background(), query, addressBookID, uri).Scan(
		&c.ID, &c.AddressBookID, &c.URI, &c.CardData, &c.LastModified, &c.Etag, &c.Size,
	)
	if err != nil {
		return nil, fmt.Errorf("contact not found: %w", err)
	}
	return &c, nil
}

func (s *BaseService) ListAllContacts() ([]Contact, error) {
	query := `SELECT id, addressbookid, uri, carddata, lastmodified, etag, size FROM dav_cards ORDER BY lastmodified DESC`
	rows, err := s.db.Query(context.Background(), query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var contacts []Contact
	for rows.Next() {
		var c Contact
		if err := rows.Scan(&c.ID, &c.AddressBookID, &c.URI, &c.CardData, &c.LastModified, &c.Etag, &c.Size); err != nil {
			return nil, err
		}
		contacts = append(contacts, c)
	}
	return contacts, nil
}

func (s *BaseService) ListRecentContacts(days int) ([]Contact, error) {
	cutoff := clock.Now().AddDate(0, 0, -days).Unix()
	query := `SELECT id, addressbookid, uri, carddata, lastmodified, etag, size FROM dav_cards WHERE lastmodified >= $1 ORDER BY lastmodified DESC`
	rows, err := s.db.Query(context.Background(), query, cutoff)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var contacts []Contact
	for rows.Next() {
		var c Contact
		if err := rows.Scan(&c.ID, &c.AddressBookID, &c.URI, &c.CardData, &c.LastModified, &c.Etag, &c.Size); err != nil {
			return nil, err
		}
		contacts = append(contacts, c)
	}
	return contacts, nil
}

// ============================================================================
// Helper Methods
// ============================================================================

func (s *BaseService) queryEventsWithContacts(query string, args ...any) ([]CalendarEvent, error) {
	rows, err := s.db.Query(context.Background(), query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var events []CalendarEvent
	for rows.Next() {
		var e CalendarEvent
		if err := rows.Scan(
			&e.ID, &e.CalendarID, &e.URI, &e.CalendarData, &e.LastModified, &e.Etag, &e.Size,
			&e.ComponentType, &e.FirstOccurence, &e.LastOccurence, &e.UID,
		); err != nil {
			return nil, err
		}
		e.ContactURIs = extractContactURIsFromICalendar(e.CalendarData)
		events = append(events, e)
	}
	return events, nil
}

func extractContactURIsFromICalendar(icalData string) []string {
	var uris []string
	for line := range strings.SplitSeq(icalData, "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "ATTENDEE") {
			parts := strings.Split(line, ":")
			if len(parts) >= 2 {
				uris = append(uris, strings.TrimSpace(parts[len(parts)-1]))
			}
		}
	}
	return uris
}

// CreateContact adds a new contact to an address book
func (s *BaseService) CreateContact(addressBookID int, userID string, input ContactInput) error {
	if s.db == nil {
		return nil // No DB configured, skip CardDAV sync
	}

	now := clock.Now().Unix()
	uri := fmt.Sprintf("%s.vcf", userID)
	cardData := GenerateVCard(input)

	tx, err := s.db.Begin(context.Background())
	if err != nil {
		return err
	}
	defer func() {
    if err := tx.Rollback(context.Background()); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	query := `
		INSERT INTO dav_cards (addressbookid, uri, carddata, lastmodified, etag, size)
		VALUES ($1, $2, $3, $4, $5, $6)
	`
	_, err = tx.Exec(context.Background(), query,
		addressBookID,
		uri,
		cardData,
		now,
		fmt.Sprintf("%d", now),
		len(cardData),
	)
	if err != nil {
		return err
	}

	return tx.Commit(context.Background())
}

// UpdateContact updates an existing contact by URI
func (s *BaseService) UpdateContact(addressBookID int, uri string, input ContactInput) error {
	now := clock.Now().Unix()
	cardData := GenerateVCard(input)

	tx, err := s.db.Begin(context.Background())
	if err != nil {
		return err
	}
	defer func() {
    if err := tx.Rollback(context.Background()); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	query := `
		UPDATE dav_cards
		SET carddata = $1, lastmodified = $2, etag = $3, size = $4
		WHERE addressbookid = $5 AND uri = $6
	`
	_, err = tx.Exec(context.Background(), query, cardData, now, fmt.Sprintf("%d", now), len(cardData), addressBookID, uri)
	if err != nil {
		return err
	}

	return tx.Commit(context.Background())
}

// DeleteContact deletes a contact by URI
func (s *BaseService) DeleteContact(addressBookID int, uri string) error {
	tx, err := s.db.Begin(context.Background())
	if err != nil {
		return err
	}
	defer func() {
    if err := tx.Rollback(context.Background()); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	query := `DELETE FROM dav_cards WHERE addressbookid = $1 AND uri = $2`
	_, err = tx.Exec(context.Background(), query, addressBookID, uri)
	if err != nil {
		return err
	}

	return tx.Commit(context.Background())
}

// CreateEvent adds a new event to a calendar
func (s *BaseService) CreateEvent(calendarID int, input EventInput) error {
	uid := fmt.Sprintf("%d@example.com", clock.Now().UnixNano())
	now := clock.Now().Unix()
	calendarData := GenerateICalEvent(input)

	tx, err := s.db.Begin(context.Background())
	if err != nil {
		return err
	}
	defer func() {
    if err := tx.Rollback(context.Background()); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	query := `
		INSERT INTO dav_calendarobjects
			(calendarid, uri, calendardata, lastmodified, etag, size, componenttype,
			 firstoccurence, lastoccurence, uid)
		VALUES ($1, $2, $3, $4, $5, $6, 'VEVENT', $7, $8, $9)
	`
	_, err = tx.Exec(context.Background(), query,
		calendarID,
		uid+".ics",
		calendarData,
		now,
		fmt.Sprintf("%d", now),
		len(calendarData),
		input.Start.Unix(),
		input.End.Unix(),
		uid,
	)
	if err != nil {
		return err
	}

	return tx.Commit(context.Background())
}

// UpdateEvent updates an existing calendar event by UID
func (s *BaseService) UpdateEvent(calendarID int, uid string, input EventInput) error {
	now := clock.Now().Unix()
	calendarData := GenerateICalEvent(input)

	tx, err := s.db.Begin(context.Background())
	if err != nil {
		return err
	}
	defer func() {
    if err := tx.Rollback(context.Background()); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	query := `
		UPDATE dav_calendarobjects
		SET calendardata = $1, lastmodified = $2, etag = $3, size = $4,
		    firstoccurence = $5, lastoccurence = $6
		WHERE calendarid = $7 AND uid = $8
	`
	_, err = tx.Exec(context.Background(), query,
		calendarData, now, fmt.Sprintf("%d", now), len(calendarData),
		input.Start.Unix(), input.End.Unix(),
		calendarID, uid,
	)
	if err != nil {
		return err
	}

	return tx.Commit(context.Background())
}

// DeleteEvent deletes an event by UID
func (s *BaseService) DeleteEvent(calendarID int, uid string) error {
	tx, err := s.db.Begin(context.Background())
	if err != nil {
		return err
	}
	defer func() {
    if err := tx.Rollback(context.Background()); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	query := `DELETE FROM dav_calendarobjects WHERE calendarid = $1 AND uid = $2`
	_, err = tx.Exec(context.Background(), query, calendarID, uid)
	if err != nil {
		return err
	}

	return tx.Commit(context.Background())
}

// ListEventsForContactSQL returns all calendar events where the given contact URI is an attendee (SQL optimized)
func (s *BaseService) ListEventsForContact(contactURI string) ([]CalendarEvent, error) {
	// Use pattern matching to find events containing the contact URI in ATTENDEE lines
	likePattern := "%" + contactURI + "%"

	query := `
		SELECT id, calendarid, uri, calendardata, lastmodified, etag, size, componenttype,
		       firstoccurence, lastoccurence, uid
		FROM dav_calendarobjects
		WHERE calendardata LIKE $1
		ORDER BY firstoccurence
	`

	return s.queryEventsWithContacts(query, likePattern)
}
