package zones

import (
	"io/fs"
	"sort"
)

func (m *Zones) scan(opts *ScanOptions) error {
	for _, fn := range []func(*ScanOptions) error{
		m.scanDirectory,
		m.scanMachines,
		m.scanZoneIDs,
		m.scanSort,
		m.scanGateways,
		m.scanCephMonitors,
	} {
		if err := fn(opts); err != nil {
			return err
		}
	}

	return nil
}

func (m *Zones) scanDirectory(_ *ScanOptions) error {
	// each directory is a zone
	entries, err := fs.ReadDir(m.dir, ".")
	if err != nil {
		return err
	}

	for _, e := range entries {
		if e.IsDir() {
			z := &Zone{
				zones: m,
				Name:  e.Name(),
			}

			if err := z.scan(); err != nil {
				return err
			}

			m.Zones = append(m.Zones, z)
		}
	}

	return nil
}

func (m *Zones) scanMachines(opts *ScanOptions) error {
	var err error
	m.ForEachMachine(func(p *Machine) bool {
		err = p.scan(opts)
		return err != nil
	})
	return err
}

func (m *Zones) scanZoneIDs(_ *ScanOptions) error {
	var hasMissing bool
	var lastZoneID int

	m.ForEachZone(func(z *Zone) bool {
		switch {
		case z.ID == 0:
			hasMissing = true
		case z.ID > lastZoneID:
			lastZoneID = z.ID
		}

		return false
	})

	if hasMissing {
		next := lastZoneID + 1

		m.ForEachZone(func(z *Zone) bool {
			if z.ID == 0 {
				z.ID, next = next, next+1
			}

			return false
		})
	}

	return nil
}

func (m *Zones) scanSort(_ *ScanOptions) error {
	sort.SliceStable(m.Zones, func(i, j int) bool {
		id1 := m.Zones[i].ID
		id2 := m.Zones[j].ID
		return id1 < id2
	})

	m.ForEachZone(func(z *Zone) bool {
		sort.Sort(z)
		return false
	})

	m.ForEachMachine(func(p *Machine) bool {
		sort.SliceStable(p.Rings, func(i, j int) bool {
			ri1 := p.Rings[i]
			ri2 := p.Rings[j]

			return ri1.Ring < ri2.Ring
		})

		return false
	})

	return nil
}

func (m *Zones) scanGateways(_ *ScanOptions) error {
	var err error

	m.ForEachZone(func(z *Zone) bool {
		_, _, err = z.GetGateway()
		return err != nil
	})
	return err
}

func (z *Zone) scan() error {
	// each directory is a machine
	entries, err := fs.ReadDir(z.zones.dir, z.Name)
	if err != nil {
		return err
	}

	for _, e := range entries {
		if e.IsDir() {
			m := &Machine{
				zone: z,
				Name: e.Name(),
			}

			if err := m.init(); err != nil {
				return err
			}

			z.Machines = append(z.Machines, m)
		}
	}

	return nil
}

// GetGateway returns the first gateway found, if none
// files will be created to enable the first [Machine] to
// be one
func (z *Zone) GetGateway() (*Machine, bool, error) {
	var first *Machine
	var gateway *Machine

	z.zones.ForEachMachine(func(p *Machine) bool {
		switch {
		case p.IsGateway():
			// found
			gateway = p
		case first == nil:
			// remember
			first = p
		default:
			// keep looking
		}

		return gateway != nil
	})

	switch {
	case gateway != nil:
		// found one
		return gateway, false, nil
	case first != nil:
		// make one
		if err := first.SetGateway(true); err != nil {
			return first, false, err
		}
		return first, true, nil
	default:
		// Zone without nodes?
		panic("unreachable")
	}
}